Skip to content

[GH-25] Fix persistent split/merged flags causing data corruption #41

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jul 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions src/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,9 @@ pub struct ProllyNode<const N: usize> {
pub min_chunk_size: usize,
pub max_chunk_size: usize,
pub pattern: u64,
#[serde(skip)]
pub split: bool,
#[serde(skip)]
pub merged: bool,
pub encode_types: Vec<EncodingType>,
pub encode_values: Vec<Vec<u8>>,
Expand Down Expand Up @@ -1540,4 +1542,57 @@ mod tests {
assert!(node.find(&[i], &storage).is_some());
}
}

#[test]
fn test_flags_reset_after_operations() {
// Test that split/merged flags are reset after insert/delete operations
let mut storage = InMemoryNodeStorage::<32>::default();
let mut node: ProllyNode<32> = ProllyNode::builder()
.pattern(0b1)
.min_chunk_size(2)
.max_chunk_size(4)
.build();

// Insert enough items to trigger splits
for i in 0..6 {
node.insert(vec![i], vec![i], &mut storage, Vec::new());
storage.insert_node(node.get_hash(), node.clone());
// Flags should be reset after each operation
assert!(
!node.split,
"Split flag should be reset after insert operation {}",
i
);
assert!(
!node.merged,
"Merged flag should be reset after insert operation {}",
i
);
}

// Test deletion as well
assert!(node.delete(&[0], &mut storage, Vec::new()));
assert!(
!node.split,
"Split flag should be reset after delete operation"
);
assert!(
!node.merged,
"Merged flag should be reset after delete operation"
);
}

#[test]
fn test_flags_not_serialized() {
// Test that split/merged flags are not serialized
let mut node = ProllyNode::<32>::default();
node.split = true;
node.merged = true;
let bytes = bincode::serialize(&node).unwrap();
let de: ProllyNode<32> = bincode::deserialize(&bytes).unwrap();
assert!(
!de.split && !de.merged,
"Split/merged flags should not be serialized"
);
}
}
11 changes: 9 additions & 2 deletions src/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,11 @@ impl<const N: usize> InMemoryNodeStorage<N> {

impl<const N: usize> NodeStorage<N> for InMemoryNodeStorage<N> {
fn get_node_by_hash(&self, hash: &ValueDigest<N>) -> Option<ProllyNode<N>> {
self.map.get(hash).cloned()
self.map.get(hash).cloned().map(|mut node| {
node.split = false;
node.merged = false;
node
})
}

fn insert_node(&mut self, hash: ValueDigest<N>, node: ProllyNode<N>) -> Option<()> {
Expand Down Expand Up @@ -156,7 +160,10 @@ impl<const N: usize> NodeStorage<N> for FileNodeStorage<N> {
let mut file = File::open(path).unwrap();
let mut data = Vec::new();
file.read_to_end(&mut data).unwrap();
Some(bincode::deserialize(&data).unwrap())
let mut node: ProllyNode<N> = bincode::deserialize(&data).unwrap();
node.split = false;
node.merged = false;
Some(node)
} else {
None
}
Expand Down