use crate::backend::native::NativeBackendError;
use crate::backend::native::NativeResult;
pub const MAX_DELTA: u32 = u32::MAX;
pub const MAX_ID_DIFFERENCE: i64 = MAX_DELTA as i64;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DeltaDecodeError {
DeltaOverflow { delta: u32, base_id: i64 },
InvalidDelta { delta: u32 },
}
impl std::fmt::Display for DeltaDecodeError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::DeltaOverflow { delta, base_id } => write!(
f,
"Delta overflow: delta {} would overflow when added to base_id {}",
delta, base_id
),
Self::InvalidDelta { delta } => {
write!(f, "Invalid delta value: {}", delta)
}
}
}
}
impl std::error::Error for DeltaDecodeError {}
pub fn encode_id_delta(node_id: i64, base_id: i64) -> u32 {
let difference = node_id.saturating_sub(base_id);
if difference < 0 {
0
} else if difference > MAX_ID_DIFFERENCE {
MAX_DELTA
} else {
difference as u32
}
}
pub fn decode_id_delta(delta: u32, base_id: i64) -> NativeResult<i64> {
let delta_i64 = delta as i64;
match base_id.checked_add(delta_i64) {
Some(reconstructed_id) => Ok(reconstructed_id),
None => Err(NativeBackendError::InvalidHeader {
field: "id_delta".to_string(),
reason: format!(
"Delta overflow: delta {} would overflow when added to base_id {}",
delta, base_id
),
}),
}
}
pub fn calculate_optimal_base_id(node_ids: &[i64]) -> i64 {
if node_ids.is_empty() {
return 0;
}
*node_ids.iter().min().unwrap_or(&0)
}
pub fn estimate_delta_savings(node_count: usize, _avg_delta: u32) -> usize {
let full_size = node_count * 8;
let delta_size = node_count * 4;
full_size.saturating_sub(delta_size)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_encode_id_delta_sequential() {
assert_eq!(encode_id_delta(105, 100), 5);
assert_eq!(encode_id_delta(110, 100), 10);
assert_eq!(encode_id_delta(1000, 100), 900);
}
#[test]
fn test_encode_id_delta_same_id() {
assert_eq!(encode_id_delta(100, 100), 0);
assert_eq!(encode_id_delta(0, 0), 0);
assert_eq!(encode_id_delta(i64::MAX, i64::MAX), 0);
}
#[test]
fn test_encode_id_delta_node_before_base() {
assert_eq!(encode_id_delta(95, 100), 0);
assert_eq!(encode_id_delta(-100, 0), 0);
assert_eq!(encode_id_delta(i64::MIN, i64::MAX), 0);
}
#[test]
fn test_encode_id_delta_max_difference() {
let base_id = 1000i64;
let max_node_id = base_id + MAX_ID_DIFFERENCE;
assert_eq!(encode_id_delta(max_node_id, base_id), MAX_DELTA);
let beyond_max = max_node_id + 1;
assert_eq!(encode_id_delta(beyond_max, base_id), MAX_DELTA);
}
#[test]
fn test_encode_id_delta_negative_ids() {
assert_eq!(encode_id_delta(-95, -100), 5);
assert_eq!(encode_id_delta(-90, -100), 10);
assert_eq!(encode_id_delta(-100, -100), 0);
assert_eq!(encode_id_delta(-105, -100), 0); }
#[test]
fn test_decode_id_delta_sequential() {
assert_eq!(decode_id_delta(5, 100).unwrap(), 105);
assert_eq!(decode_id_delta(10, 100).unwrap(), 110);
assert_eq!(decode_id_delta(900, 100).unwrap(), 1000);
}
#[test]
fn test_decode_id_delta_zero() {
assert_eq!(decode_id_delta(0, 100).unwrap(), 100);
assert_eq!(decode_id_delta(0, 0).unwrap(), 0);
assert_eq!(decode_id_delta(0, -100).unwrap(), -100);
}
#[test]
fn test_decode_id_delta_max_delta() {
assert_eq!(decode_id_delta(MAX_DELTA, 0).unwrap(), MAX_DELTA as i64);
assert_eq!(
decode_id_delta(MAX_DELTA, 1000).unwrap(),
1000 + MAX_DELTA as i64
);
}
#[test]
fn test_decode_id_delta_negative_base() {
assert_eq!(decode_id_delta(5, -100).unwrap(), -95);
assert_eq!(decode_id_delta(10, -100).unwrap(), -90);
assert_eq!(decode_id_delta(100, -100).unwrap(), 0);
}
#[test]
fn test_decode_id_delta_overflow_edge() {
let base_id = i64::MAX - 1000;
let delta = 500;
assert_eq!(decode_id_delta(delta, base_id).unwrap(), i64::MAX - 500);
let base_at_edge = i64::MAX - MAX_DELTA as i64;
assert_eq!(decode_id_delta(MAX_DELTA, base_at_edge).unwrap(), i64::MAX);
}
#[test]
fn test_decode_encode_roundtrip() {
let test_cases = vec![
(100, 100, 0),
(105, 100, 5),
(1000, 100, 900),
(0, 0, 0),
(-95, -100, 5),
(i64::MAX, i64::MAX, 0),
];
for (node_id, base_id, expected_delta) in test_cases {
let delta = encode_id_delta(node_id, base_id);
assert_eq!(delta, expected_delta);
let reconstructed = decode_id_delta(delta, base_id).unwrap();
if node_id >= base_id && (node_id - base_id) <= MAX_ID_DIFFERENCE {
assert_eq!(reconstructed, node_id);
}
}
}
#[test]
fn test_calculate_optimal_base_id() {
let ids = vec![100, 105, 110, 115];
assert_eq!(calculate_optimal_base_id(&ids), 100);
let ids = vec![1000, 1001, 1002];
assert_eq!(calculate_optimal_base_id(&ids), 1000);
let ids = vec![-100, -50, 0, 50, 100];
assert_eq!(calculate_optimal_base_id(&ids), -100);
let ids = vec![42];
assert_eq!(calculate_optimal_base_id(&ids), 42);
let ids: Vec<i64> = vec![];
assert_eq!(calculate_optimal_base_id(&ids), 0);
}
#[test]
fn test_estimate_delta_savings() {
let sequential_savings = estimate_delta_savings(1000, 1);
assert_eq!(sequential_savings, 4000);
let large_delta_savings = estimate_delta_savings(1000, 10000);
assert_eq!(large_delta_savings, 4000);
assert_eq!(estimate_delta_savings(0, 0), 0);
}
#[test]
fn test_delta_with_extreme_values() {
let min_id = i64::MIN;
let max_id = i64::MAX;
assert_eq!(encode_id_delta(min_id, min_id), 0);
assert_eq!(encode_id_delta(min_id + 100, min_id), 100);
assert_eq!(encode_id_delta(max_id, max_id), 0);
let base_near_max = max_id - 1000;
assert_eq!(encode_id_delta(max_id, base_near_max), 1000);
assert_eq!(encode_id_delta(max_id, base_near_max + 500), 500);
}
}