Skip to main content

oxirs_tdb/compression/
delta.rs

1//! Delta encoding for sequences
2
3use crate::compression::{
4    AdvancedCompressionType, CompressedData, CompressionAlgorithm, CompressionMetadata,
5};
6use anyhow::{anyhow, Result};
7use std::collections::HashMap;
8use std::time::Instant;
9
10/// Delta encoding for sequences
11pub struct DeltaEncoder;
12
13impl DeltaEncoder {
14    /// Encode sequence using delta encoding
15    pub fn encode_u64_sequence(values: &[u64]) -> Result<Vec<u8>> {
16        if values.is_empty() {
17            return Ok(Vec::new());
18        }
19
20        let mut encoded = Vec::new();
21
22        // Store first value as-is
23        encoded.extend_from_slice(&values[0].to_le_bytes());
24
25        // Store deltas
26        for i in 1..values.len() {
27            let delta = if values[i] >= values[i - 1] {
28                (values[i] - values[i - 1]) << 1 // Positive delta
29            } else {
30                ((values[i - 1] - values[i]) << 1) | 1 // Negative delta with flag
31            };
32            encoded.extend_from_slice(&delta.to_le_bytes());
33        }
34
35        Ok(encoded)
36    }
37
38    /// Decode delta encoded sequence
39    pub fn decode_u64_sequence(encoded: &[u8]) -> Result<Vec<u64>> {
40        if encoded.is_empty() {
41            return Ok(Vec::new());
42        }
43
44        if encoded.len() % 8 != 0 {
45            return Err(anyhow!("Invalid delta encoded data length"));
46        }
47
48        let mut values = Vec::new();
49        let chunks: Vec<_> = encoded.chunks_exact(8).collect();
50
51        // First value
52        let first_value = u64::from_le_bytes([
53            chunks[0][0],
54            chunks[0][1],
55            chunks[0][2],
56            chunks[0][3],
57            chunks[0][4],
58            chunks[0][5],
59            chunks[0][6],
60            chunks[0][7],
61        ]);
62        values.push(first_value);
63
64        let mut current_value = first_value;
65
66        // Process deltas
67        for chunk in &chunks[1..] {
68            let encoded_delta = u64::from_le_bytes([
69                chunk[0], chunk[1], chunk[2], chunk[3], chunk[4], chunk[5], chunk[6], chunk[7],
70            ]);
71
72            let is_negative = (encoded_delta & 1) == 1;
73            let delta = encoded_delta >> 1;
74
75            current_value = if is_negative {
76                current_value.saturating_sub(delta)
77            } else {
78                current_value.saturating_add(delta)
79            };
80
81            values.push(current_value);
82        }
83
84        Ok(values)
85    }
86
87    /// Encode byte sequence using delta encoding
88    pub fn encode_byte_sequence(data: &[u8]) -> Result<Vec<u8>> {
89        if data.is_empty() {
90            return Ok(Vec::new());
91        }
92
93        let mut encoded = Vec::new();
94        encoded.push(data[0]);
95
96        for i in 1..data.len() {
97            let delta = data[i] as i16 - data[i - 1] as i16;
98            encoded.extend_from_slice(&delta.to_le_bytes());
99        }
100
101        Ok(encoded)
102    }
103
104    /// Decode byte sequence using delta encoding
105    pub fn decode_byte_sequence(encoded: &[u8]) -> Result<Vec<u8>> {
106        if encoded.is_empty() {
107            return Ok(Vec::new());
108        }
109
110        let mut decoded = Vec::new();
111        decoded.push(encoded[0]);
112
113        let mut current_value = encoded[0] as i16;
114
115        for chunk in encoded[1..].chunks_exact(2) {
116            let delta = i16::from_le_bytes([chunk[0], chunk[1]]);
117            current_value = (current_value + delta).clamp(0, 255);
118            decoded.push(current_value as u8);
119        }
120
121        Ok(decoded)
122    }
123
124    /// Estimate compression ratio for byte sequences
125    pub fn estimate_byte_compression_ratio(data: &[u8]) -> f64 {
126        if data.len() < 2 {
127            return 1.0;
128        }
129
130        let compressed_size = 1 + (data.len() - 1) * 2; // First byte + deltas
131        compressed_size as f64 / data.len() as f64
132    }
133
134    /// Encode a sorted u64 sequence using variable-length (LEB128-style) delta encoding.
135    ///
136    /// This produces more compact output than `encode_u64_sequence` for small
137    /// deltas (which is typical for sorted triple IDs).
138    ///
139    /// Encoding:
140    /// - First value: stored as 9-byte little-endian u64 (1 header byte = 0xFF + 8 bytes)
141    /// - Subsequent values: zigzag-encode the signed delta, then store as LEB128 varint
142    pub fn encode(values: &[u64]) -> Vec<u8> {
143        if values.is_empty() {
144            return Vec::new();
145        }
146
147        let mut out = Vec::new();
148
149        // First value as raw 8-byte LE
150        out.extend_from_slice(&values[0].to_le_bytes());
151
152        let mut prev = values[0];
153        for &v in &values[1..] {
154            // Signed delta, zigzag encoded into unsigned for LEB128
155            let delta: i64 = if v >= prev {
156                (v - prev) as i64
157            } else {
158                -((prev - v) as i64)
159            };
160            // Zigzag: map signed to unsigned
161            let zigzag: u64 = if delta >= 0 {
162                (delta as u64) << 1
163            } else {
164                ((!delta as u64) << 1) | 1
165            };
166            // LEB128 encode the zigzag value
167            encode_leb128(&mut out, zigzag);
168            prev = v;
169        }
170
171        out
172    }
173
174    /// Decode a variable-length delta encoded u64 sequence produced by `encode`.
175    pub fn decode(data: &[u8]) -> Result<Vec<u64>> {
176        if data.is_empty() {
177            return Ok(Vec::new());
178        }
179
180        if data.len() < 8 {
181            return Err(anyhow!("Delta-encoded data too short for first value"));
182        }
183
184        let first = u64::from_le_bytes(
185            data[..8]
186                .try_into()
187                .map_err(|_| anyhow!("Failed to read first value"))?,
188        );
189
190        let mut values = vec![first];
191        let mut prev = first;
192        let mut pos = 8;
193
194        while pos < data.len() {
195            let (zigzag, consumed) = decode_leb128(&data[pos..])
196                .ok_or_else(|| anyhow!("Invalid LEB128 encoding at byte {}", pos))?;
197            pos += consumed;
198
199            // Reverse zigzag
200            let delta: i64 = if (zigzag & 1) == 0 {
201                (zigzag >> 1) as i64
202            } else {
203                !((zigzag >> 1) as i64)
204            };
205
206            prev = if delta >= 0 {
207                prev.saturating_add(delta as u64)
208            } else {
209                prev.saturating_sub((-delta) as u64)
210            };
211            values.push(prev);
212        }
213
214        Ok(values)
215    }
216
217    /// Estimate compression ratio for u64 sequences
218    pub fn estimate_u64_compression_ratio(values: &[u64]) -> f64 {
219        if values.len() < 2 {
220            return 1.0;
221        }
222
223        // Estimate based on typical delta sizes
224        let mut small_deltas = 0;
225        for i in 1..values.len() {
226            let delta = values[i].abs_diff(values[i - 1]);
227            if delta < 65536 {
228                small_deltas += 1;
229            }
230        }
231
232        // Conservative estimate - in practice delta encoding can be much better
233        // for sorted sequences
234        if small_deltas > values.len() / 2 {
235            0.5 // Good compression expected
236        } else {
237            1.0 // No compression expected
238        }
239    }
240}
241
242// ─────────────────────────────────────────────────────────────────────────────
243// LEB128 helpers
244// ─────────────────────────────────────────────────────────────────────────────
245
246/// Encode a `u64` value as unsigned LEB128 into `buf`.
247fn encode_leb128(buf: &mut Vec<u8>, mut value: u64) {
248    loop {
249        let byte = (value & 0x7F) as u8;
250        value >>= 7;
251        if value == 0 {
252            buf.push(byte);
253            break;
254        } else {
255            buf.push(byte | 0x80);
256        }
257    }
258}
259
260/// Decode an unsigned LEB128 value from `data`.
261///
262/// Returns `(value, bytes_consumed)` or `None` if the data is empty or malformed.
263fn decode_leb128(data: &[u8]) -> Option<(u64, usize)> {
264    let mut value: u64 = 0;
265    let mut shift: u32 = 0;
266    for (i, &byte) in data.iter().enumerate() {
267        let low = (byte & 0x7F) as u64;
268        value |= low.checked_shl(shift)?;
269        shift += 7;
270        if (byte & 0x80) == 0 {
271            return Some((value, i + 1));
272        }
273        if shift > 63 {
274            return None; // overflow protection
275        }
276    }
277    None // unterminated LEB128
278}
279
280impl CompressionAlgorithm for DeltaEncoder {
281    fn compress(&self, data: &[u8]) -> Result<CompressedData> {
282        let start = Instant::now();
283        let compressed_bytes = Self::encode_byte_sequence(data)?;
284        let compression_time = start.elapsed();
285
286        let mut metadata_map = HashMap::new();
287        if !data.is_empty() {
288            metadata_map.insert("first_value".to_string(), data[0].to_string());
289            metadata_map.insert(
290                "delta_count".to_string(),
291                (data.len().saturating_sub(1)).to_string(),
292            );
293        }
294
295        let metadata = CompressionMetadata {
296            algorithm: AdvancedCompressionType::Delta,
297            original_size: data.len() as u64,
298            compressed_size: compressed_bytes.len() as u64,
299            compression_time_us: compression_time.as_micros() as u64,
300            metadata: metadata_map,
301        };
302
303        Ok(CompressedData {
304            data: compressed_bytes,
305            metadata,
306        })
307    }
308
309    fn decompress(&self, compressed: &CompressedData) -> Result<Vec<u8>> {
310        if compressed.metadata.algorithm != AdvancedCompressionType::Delta {
311            return Err(anyhow!(
312                "Invalid compression algorithm: expected Delta, got {}",
313                compressed.metadata.algorithm
314            ));
315        }
316
317        Self::decode_byte_sequence(&compressed.data)
318    }
319
320    fn algorithm_type(&self) -> AdvancedCompressionType {
321        AdvancedCompressionType::Delta
322    }
323}
324
325#[cfg(test)]
326mod tests {
327    use super::*;
328
329    #[test]
330    fn test_u64_delta_encoding() {
331        let values = vec![100, 102, 105, 103, 110];
332        let encoded = DeltaEncoder::encode_u64_sequence(&values).unwrap();
333        let decoded = DeltaEncoder::decode_u64_sequence(&encoded).unwrap();
334        assert_eq!(values, decoded);
335    }
336
337    #[test]
338    fn test_byte_delta_encoding() {
339        let data = vec![50, 52, 55, 53, 60];
340        let encoded = DeltaEncoder::encode_byte_sequence(&data).unwrap();
341        let decoded = DeltaEncoder::decode_byte_sequence(&encoded).unwrap();
342        assert_eq!(data, decoded);
343    }
344
345    #[test]
346    fn test_empty_sequence() {
347        let data = vec![];
348        let encoded = DeltaEncoder::encode_byte_sequence(&data).unwrap();
349        assert!(encoded.is_empty());
350    }
351
352    #[test]
353    fn test_single_value() {
354        let data = vec![42];
355        let encoded = DeltaEncoder::encode_byte_sequence(&data).unwrap();
356        let decoded = DeltaEncoder::decode_byte_sequence(&encoded).unwrap();
357        assert_eq!(data, decoded);
358    }
359
360    #[test]
361    fn test_compression_algorithm_trait() {
362        let encoder = DeltaEncoder;
363        let data = vec![10, 12, 15, 13, 20];
364
365        let compressed = encoder.compress(&data).unwrap();
366        assert_eq!(
367            compressed.metadata.algorithm,
368            AdvancedCompressionType::Delta
369        );
370
371        let decompressed = encoder.decompress(&compressed).unwrap();
372        assert_eq!(data, decompressed);
373    }
374
375    #[test]
376    fn test_sorted_sequence() {
377        let values = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
378        let encoded = DeltaEncoder::encode_u64_sequence(&values).unwrap();
379        let decoded = DeltaEncoder::decode_u64_sequence(&encoded).unwrap();
380        assert_eq!(values, decoded);
381    }
382
383    #[test]
384    fn test_decreasing_sequence() {
385        let values = vec![100, 90, 80, 70, 60];
386        let encoded = DeltaEncoder::encode_u64_sequence(&values).unwrap();
387        let decoded = DeltaEncoder::decode_u64_sequence(&encoded).unwrap();
388        assert_eq!(values, decoded);
389    }
390
391    // ── Variable-length delta encode/decode ──────────────────────────────────
392
393    #[test]
394    fn test_vle_encode_empty() {
395        let encoded = DeltaEncoder::encode(&[]);
396        assert!(encoded.is_empty());
397    }
398
399    #[test]
400    fn test_vle_decode_empty() {
401        let decoded = DeltaEncoder::decode(&[]).unwrap();
402        assert!(decoded.is_empty());
403    }
404
405    #[test]
406    fn test_vle_single_value() {
407        let values = vec![42u64];
408        let encoded = DeltaEncoder::encode(&values);
409        let decoded = DeltaEncoder::decode(&encoded).unwrap();
410        assert_eq!(decoded, values);
411    }
412
413    #[test]
414    fn test_vle_sorted_ascending() {
415        let values: Vec<u64> = (1..=20).collect();
416        let encoded = DeltaEncoder::encode(&values);
417        let decoded = DeltaEncoder::decode(&encoded).unwrap();
418        assert_eq!(decoded, values);
419    }
420
421    #[test]
422    fn test_vle_sorted_descending() {
423        let values: Vec<u64> = (1..=20).rev().collect();
424        let encoded = DeltaEncoder::encode(&values);
425        let decoded = DeltaEncoder::decode(&encoded).unwrap();
426        assert_eq!(decoded, values);
427    }
428
429    #[test]
430    fn test_vle_with_duplicates() {
431        let values = vec![5u64, 5, 5, 5, 10, 10, 15];
432        let encoded = DeltaEncoder::encode(&values);
433        let decoded = DeltaEncoder::decode(&encoded).unwrap();
434        assert_eq!(decoded, values);
435    }
436
437    #[test]
438    fn test_vle_large_sequence_roundtrip() {
439        let values: Vec<u64> = (0..1000).map(|i| i * 7).collect();
440        let encoded = DeltaEncoder::encode(&values);
441        let decoded = DeltaEncoder::decode(&encoded).unwrap();
442        assert_eq!(decoded, values);
443    }
444
445    #[test]
446    fn test_vle_large_gaps() {
447        let values = vec![0u64, 1_000_000, 2_000_000, 3_000_000];
448        let encoded = DeltaEncoder::encode(&values);
449        let decoded = DeltaEncoder::decode(&encoded).unwrap();
450        assert_eq!(decoded, values);
451    }
452
453    #[test]
454    fn test_vle_compact_for_small_deltas() {
455        // For a sequence with small deltas, VLE should be more compact than
456        // the fixed 8-byte encoding for sequences longer than 1 element.
457        let values: Vec<u64> = (100..200).collect(); // deltas of 1
458        let vle_encoded = DeltaEncoder::encode(&values);
459        // Fixed encoding: 100 values × 8 bytes = 800 bytes
460        // VLE: 8 bytes (first) + 99 × 1 byte (delta=1 encodes to 1 LEB128 byte) = 107 bytes
461        assert!(vle_encoded.len() < 100 * 8, "VLE should be more compact");
462    }
463
464    // ── LEB128 helpers ───────────────────────────────────────────────────────
465
466    #[test]
467    fn test_leb128_round_trip_small() {
468        let mut buf = Vec::new();
469        encode_leb128(&mut buf, 127);
470        let (v, n) = decode_leb128(&buf).unwrap();
471        assert_eq!(v, 127);
472        assert_eq!(n, 1);
473    }
474
475    #[test]
476    fn test_leb128_round_trip_large() {
477        let mut buf = Vec::new();
478        encode_leb128(&mut buf, u64::MAX);
479        let (v, _) = decode_leb128(&buf).unwrap();
480        assert_eq!(v, u64::MAX);
481    }
482
483    #[test]
484    fn test_leb128_zero() {
485        let mut buf = Vec::new();
486        encode_leb128(&mut buf, 0);
487        assert_eq!(buf.len(), 1);
488        let (v, _) = decode_leb128(&buf).unwrap();
489        assert_eq!(v, 0);
490    }
491}