use cypherlite_core::{NodeRecord, RelationshipRecord};
use std::collections::BTreeMap;
#[derive(Debug, Clone, PartialEq)]
pub enum VersionRecord {
Node(NodeRecord),
Relationship(RelationshipRecord),
}
pub struct VersionStore {
versions: BTreeMap<(u64, u64), VersionRecord>,
next_seq: BTreeMap<u64, u64>,
}
impl VersionStore {
pub fn new() -> Self {
Self {
versions: BTreeMap::new(),
next_seq: BTreeMap::new(),
}
}
pub fn snapshot_node(&mut self, entity_id: u64, record: NodeRecord) -> u64 {
let seq = self.next_seq.entry(entity_id).or_insert(1);
let current_seq = *seq;
self.versions
.insert((entity_id, current_seq), VersionRecord::Node(record));
*seq += 1;
current_seq
}
pub fn snapshot_relationship(&mut self, entity_id: u64, record: RelationshipRecord) -> u64 {
let seq = self.next_seq.entry(entity_id).or_insert(1);
let current_seq = *seq;
self.versions.insert(
(entity_id, current_seq),
VersionRecord::Relationship(record),
);
*seq += 1;
current_seq
}
pub fn get_version(&self, entity_id: u64, version_seq: u64) -> Option<&VersionRecord> {
self.versions.get(&(entity_id, version_seq))
}
pub fn get_latest_version(&self, entity_id: u64) -> Option<&VersionRecord> {
let current_seq = self.next_seq.get(&entity_id)?;
if *current_seq <= 1 {
return None;
}
self.versions.get(&(entity_id, *current_seq - 1))
}
pub fn get_version_chain(&self, entity_id: u64) -> Vec<(u64, &VersionRecord)> {
self.versions
.range((entity_id, 0)..=(entity_id, u64::MAX))
.map(|((_, seq), record)| (*seq, record))
.collect()
}
pub fn version_count(&self, entity_id: u64) -> u64 {
self.next_seq.get(&entity_id).copied().unwrap_or(1) - 1
}
pub fn total_versions(&self) -> usize {
self.versions.len()
}
}
impl Default for VersionStore {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use cypherlite_core::{Direction, EdgeId, NodeId, PropertyValue};
fn sample_node(id: u64, name: &str) -> NodeRecord {
NodeRecord {
node_id: NodeId(id),
labels: vec![1],
properties: vec![(1, PropertyValue::String(name.to_string()))],
next_edge_id: None,
overflow_page: None,
}
}
fn sample_edge(id: u64) -> RelationshipRecord {
RelationshipRecord {
edge_id: EdgeId(id),
start_node: NodeId(1),
end_node: NodeId(2),
rel_type_id: 1,
direction: Direction::Outgoing,
next_out_edge: None,
next_in_edge: None,
properties: vec![(1, PropertyValue::String("v1".to_string()))],
#[cfg(feature = "subgraph")]
start_is_subgraph: false,
#[cfg(feature = "subgraph")]
end_is_subgraph: false,
}
}
#[test]
fn test_version_store_new_is_empty() {
let store = VersionStore::new();
assert_eq!(store.total_versions(), 0);
}
#[test]
fn test_snapshot_node() {
let mut store = VersionStore::new();
let node = sample_node(1, "Alice");
let seq = store.snapshot_node(1, node.clone());
assert_eq!(seq, 1);
assert_eq!(store.total_versions(), 1);
let version = store.get_version(1, 1).expect("version exists");
assert_eq!(*version, VersionRecord::Node(node));
}
#[test]
fn test_snapshot_relationship() {
let mut store = VersionStore::new();
let edge = sample_edge(1);
let seq = store.snapshot_relationship(1, edge.clone());
assert_eq!(seq, 1);
let version = store.get_version(1, 1).expect("version exists");
assert_eq!(*version, VersionRecord::Relationship(edge));
}
#[test]
fn test_multiple_snapshots_incrementing_seq() {
let mut store = VersionStore::new();
let seq1 = store.snapshot_node(1, sample_node(1, "v1"));
let seq2 = store.snapshot_node(1, sample_node(1, "v2"));
let seq3 = store.snapshot_node(1, sample_node(1, "v3"));
assert_eq!(seq1, 1);
assert_eq!(seq2, 2);
assert_eq!(seq3, 3);
assert_eq!(store.version_count(1), 3);
}
#[test]
fn test_version_chain() {
let mut store = VersionStore::new();
store.snapshot_node(1, sample_node(1, "v1"));
store.snapshot_node(1, sample_node(1, "v2"));
store.snapshot_node(1, sample_node(1, "v3"));
let chain = store.get_version_chain(1);
assert_eq!(chain.len(), 3);
assert_eq!(chain[0].0, 1); assert_eq!(chain[2].0, 3); }
#[test]
fn test_get_latest_version() {
let mut store = VersionStore::new();
store.snapshot_node(1, sample_node(1, "v1"));
store.snapshot_node(1, sample_node(1, "v2"));
let latest = store.get_latest_version(1).expect("latest exists");
match latest {
VersionRecord::Node(n) => {
assert_eq!(n.properties[0].1, PropertyValue::String("v2".to_string()));
}
_ => panic!("expected node version"),
}
}
#[test]
fn test_get_latest_version_nonexistent() {
let store = VersionStore::new();
assert!(store.get_latest_version(999).is_none());
}
#[test]
fn test_version_chain_empty() {
let store = VersionStore::new();
let chain = store.get_version_chain(999);
assert!(chain.is_empty());
}
#[test]
fn test_independent_sequences_per_entity() {
let mut store = VersionStore::new();
let s1 = store.snapshot_node(1, sample_node(1, "A"));
let s2 = store.snapshot_node(2, sample_node(2, "B"));
let s3 = store.snapshot_node(1, sample_node(1, "A2"));
assert_eq!(s1, 1);
assert_eq!(s2, 1); assert_eq!(s3, 2);
assert_eq!(store.version_count(1), 2);
assert_eq!(store.version_count(2), 1);
}
#[test]
fn test_version_store_default() {
let store = VersionStore::default();
assert_eq!(store.total_versions(), 0);
}
#[test]
fn test_version_count_unseen_entity() {
let store = VersionStore::new();
assert_eq!(store.version_count(999), 0);
}
}