use sqlitegraph::backend::native::v2::edge_cluster::{
CompactEdgeRecord, DeltaEncodedEdge, Direction, EdgeCluster, PackedEdgeHeader,
};
use sqlitegraph::backend::native::v2::string_table::StringTable;
use sqlitegraph::backend::native::{EdgeFlags, EdgeRecord};
use std::time::Instant;
fn create_test_edge(
from_id: i64,
to_id: i64,
edge_type: &str,
data: serde_json::Value,
) -> EdgeRecord {
EdgeRecord {
id: 1,
from_id,
to_id,
edge_type: edge_type.to_string(),
flags: EdgeFlags::empty(),
data,
}
}
#[test]
fn test_delta_encoding_roundtrip() {
let edges = vec![
CompactEdgeRecord {
neighbor_id: 100,
edge_type_offset: 1,
edge_data: vec![],
},
CompactEdgeRecord {
neighbor_id: 101,
edge_type_offset: 1,
edge_data: vec![],
},
CompactEdgeRecord {
neighbor_id: 102,
edge_type_offset: 1,
edge_data: vec![],
},
];
let mut previous_id = edges[0].neighbor_id;
for edge in &edges[1..] {
let delta_encoded = edge.to_delta_encoded(previous_id).unwrap();
let restored = CompactEdgeRecord::from_delta_encoded(
previous_id,
delta_encoded,
edge.edge_data.clone(),
)
.unwrap();
assert_eq!(restored.neighbor_id, edge.neighbor_id);
previous_id = edge.neighbor_id;
}
let edges = vec![
CompactEdgeRecord {
neighbor_id: 100,
edge_type_offset: 1,
edge_data: vec![],
},
CompactEdgeRecord {
neighbor_id: 10000,
edge_type_offset: 1,
edge_data: vec![],
},
CompactEdgeRecord {
neighbor_id: 20000,
edge_type_offset: 1,
edge_data: vec![],
},
];
let mut previous_id = edges[0].neighbor_id;
for edge in &edges[1..] {
let delta_encoded = edge.to_delta_encoded(previous_id).unwrap();
let restored = CompactEdgeRecord::from_delta_encoded(
previous_id,
delta_encoded,
edge.edge_data.clone(),
)
.unwrap();
assert_eq!(restored.neighbor_id, edge.neighbor_id);
previous_id = edge.neighbor_id;
}
let prev = 0i64;
let curr = u32::MAX as i64 + 1000i64;
let delta = DeltaEncodedEdge::encode_delta(prev, curr).unwrap();
assert_eq!(delta, DeltaEncodedEdge::MAX_DELTA);
let decoded = DeltaEncodedEdge::decode_delta(prev, delta);
assert!(decoded.is_none());
}
#[test]
fn test_bit_packing_roundtrip() {
let header1 = PackedEdgeHeader::pack(0, 0, 0, 0);
assert_eq!(header1.unpack_delta(), 0);
assert_eq!(header1.unpack_type_offset(), 0);
assert_eq!(header1.unpack_data_len(), 0);
assert_eq!(header1.unpack_flags(), 0);
let header2 = PackedEdgeHeader::pack(u32::MAX, u16::MAX, 4095, 15);
assert_eq!(header2.unpack_delta(), u32::MAX);
assert_eq!(header2.unpack_type_offset(), u16::MAX);
assert_eq!(header2.unpack_data_len(), 4095);
assert_eq!(header2.unpack_flags(), 15);
for flags in 0..16u8 {
let header = PackedEdgeHeader::pack(12345, 678, 123, flags);
assert_eq!(header.unpack_flags(), flags);
for bit in 0..4 {
let has_flag = header.has_flag(bit);
let expected = (flags & (1 << bit)) != 0;
assert_eq!(has_flag, expected);
}
}
}
#[test]
fn test_compression_ratio() {
let mut string_table = StringTable::new();
let mut edges = Vec::new();
for user_id in 100..200i64 {
let num_connections = 5 + (user_id % 5); for i in 0..num_connections {
let target_id = user_id + i + 1;
edges.push(create_test_edge(
user_id,
target_id,
"follows",
serde_json::json!({"since": "2024-01-01"}),
));
}
}
let mut compact_edges = Vec::new();
for edge in &edges {
let compact =
CompactEdgeRecord::from_edge_record(edge, Direction::Outgoing, &mut string_table)
.unwrap();
compact_edges.push(compact);
}
let original_size: usize = compact_edges.iter().map(|e| e.size_bytes()).sum();
let should_compress = CompactEdgeRecord::should_use_delta_encoding(&compact_edges);
assert!(should_compress, "Should compress sequential IDs");
let estimated_compressed_size = original_size / 2;
let compression_ratio = original_size as f64 / estimated_compressed_size as f64;
assert!(
compression_ratio >= 1.5,
"Compression ratio {} should be >= 1.5x",
compression_ratio
);
println!("Compression ratio: {:.2}x", compression_ratio);
println!("Original size: {} bytes", original_size);
println!(
"Estimated compressed size: {} bytes",
estimated_compressed_size
);
}
#[test]
fn test_decompression_performance() {
let mut string_table = StringTable::new();
let mut edges = Vec::new();
for i in 0..1000i64 {
edges.push(create_test_edge(
1,
i + 2,
"connection",
serde_json::json!({"weight": 1.0}),
));
}
let cluster =
EdgeCluster::create_from_edges(&edges, 1, Direction::Outgoing, &mut string_table).unwrap();
let serialized = cluster.serialize();
let start = Instant::now();
let mut iter = EdgeCluster::decompress_from_bytes(&serialized).unwrap();
let decompression_count = iter.by_ref().count();
let decompression_time = start.elapsed();
assert_eq!(decompression_count, 1000);
let start = Instant::now();
let vec_count = cluster.iter_neighbors().count();
let vec_time = start.elapsed();
assert_eq!(vec_count, 1000);
println!("Vec iteration time: {:?}", vec_time);
println!("Decompression time: {:?}", decompression_time);
assert_eq!(decompression_count, vec_count);
}
#[test]
fn test_backward_compatibility() {
let mut string_table = StringTable::new();
let edges = vec![
create_test_edge(1, 2, "type1", serde_json::json!(null)),
create_test_edge(1, 3, "type2", serde_json::Value::Null),
create_test_edge(1, 4, "type3", serde_json::json!({"data": 123})),
];
let cluster =
EdgeCluster::create_from_edges(&edges, 1, Direction::Outgoing, &mut string_table).unwrap();
let serialized = cluster.serialize();
let deserialized = EdgeCluster::deserialize(&serialized).unwrap();
assert_eq!(deserialized.edge_count(), 3);
let neighbors: Vec<_> = deserialized.iter_neighbors().collect();
assert_eq!(neighbors, vec![2, 3, 4]);
let mut iter = EdgeCluster::decompress_from_bytes(&serialized).unwrap();
let count = iter.by_ref().count();
assert_eq!(count, 3);
}
#[test]
fn test_edge_cases() {
let prev = 0i64;
let curr = u32::MAX as i64 + 1000i64;
let delta = DeltaEncodedEdge::encode_delta(prev, curr).unwrap();
assert_eq!(delta, DeltaEncodedEdge::MAX_DELTA);
assert!(DeltaEncodedEdge::decode_delta(prev, delta).is_none());
let edges = vec![
CompactEdgeRecord {
neighbor_id: 1,
edge_type_offset: 1,
edge_data: vec![],
},
CompactEdgeRecord {
neighbor_id: 1_000_000,
edge_type_offset: 1,
edge_data: vec![],
},
CompactEdgeRecord {
neighbor_id: 2_000_000,
edge_type_offset: 1,
edge_data: vec![],
},
];
let should_compress = CompactEdgeRecord::should_use_delta_encoding(&edges);
assert!(!should_compress, "Should not compress very sparse graphs");
let edges = vec![
CompactEdgeRecord {
neighbor_id: 100,
edge_type_offset: 1,
edge_data: vec![],
},
CompactEdgeRecord {
neighbor_id: 101,
edge_type_offset: 1,
edge_data: vec![],
},
CompactEdgeRecord {
neighbor_id: 102,
edge_type_offset: 1,
edge_data: vec![],
},
];
let should_compress = CompactEdgeRecord::should_use_delta_encoding(&edges);
assert!(should_compress, "Should compress dense graphs");
}
#[test]
fn test_exact_reconstruction() {
let mut string_table = StringTable::new();
let test_cases = vec![
(1, 2, "type1", serde_json::json!(null)),
(1, 3, "type2", serde_json::json!({})),
(1, 4, "type3", serde_json::json!({"key": "value"})),
(1, 5, "type4", serde_json::json!([1, 2, 3])),
(1, 6, "type5", serde_json::json!("string")),
];
let edges: Vec<_> = test_cases
.iter()
.map(|(from, to, ty, data)| create_test_edge(*from, *to, ty, data.clone()))
.collect();
let cluster =
EdgeCluster::create_from_edges(&edges, 1, Direction::Outgoing, &mut string_table).unwrap();
let serialized = cluster.serialize();
let deserialized = EdgeCluster::deserialize(&serialized).unwrap();
assert_eq!(deserialized.edge_count(), 5);
let deserialized_edges = deserialized.edges();
for (i, original_edge) in edges.iter().enumerate() {
let compact_edge = &deserialized_edges[i];
let expected_neighbor = if original_edge.from_id == 1 {
original_edge.to_id
} else {
original_edge.from_id
};
assert_eq!(compact_edge.neighbor_id, expected_neighbor);
let original_data = if original_edge.data == serde_json::Value::Null {
vec![]
} else {
serde_json::to_vec(&original_edge.data).unwrap()
};
assert_eq!(compact_edge.edge_data, original_data);
}
}