use serde::{Serialize, Deserialize};
use crate::types::{Node, Edge, NodeId, EdgeId};
use crate::error::Result;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VersionedNode {
pub id: NodeId,
pub version: u64,
pub timestamp: u64,
pub node_data: Node,
pub prev_version: Option<u64>,
pub valid_from: u64,
pub valid_to: Option<u64>,
}
impl VersionedNode {
pub fn new(node: Node, timestamp: u64) -> Self {
Self {
id: node.id,
version: 1,
timestamp,
node_data: node,
prev_version: None,
valid_from: timestamp,
valid_to: None,
}
}
pub fn new_version(
previous: &VersionedNode,
new_data: Node,
timestamp: u64,
) -> Self {
Self {
id: previous.id,
version: previous.version + 1,
timestamp,
node_data: new_data,
prev_version: Some(previous.version),
valid_from: timestamp,
valid_to: None,
}
}
pub fn invalidate(&mut self, timestamp: u64) {
self.valid_to = Some(timestamp);
}
pub fn is_valid_at(&self, timestamp: u64) -> bool {
timestamp >= self.valid_from
&& self.valid_to.map(|to| timestamp < to).unwrap_or(true)
}
pub fn is_gc_eligible(&self, cutoff_timestamp: u64) -> bool {
match self.valid_to {
Some(valid_to) => valid_to < cutoff_timestamp,
None => false, }
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VersionedEdge {
pub id: EdgeId,
pub version: u64,
pub timestamp: u64,
pub edge_data: Edge,
pub valid_from: u64,
pub valid_to: Option<u64>,
pub prev_version: Option<u64>,
}
impl VersionedEdge {
pub fn new(edge: Edge, timestamp: u64) -> Self {
Self {
id: edge.id,
version: 1,
timestamp,
edge_data: edge,
valid_from: timestamp,
valid_to: None,
prev_version: None,
}
}
pub fn with_valid_to(mut self, ts: u64) -> Self {
self.valid_to = Some(ts);
self
}
pub fn is_valid_at(&self, timestamp: u64) -> bool {
timestamp >= self.valid_from
&& self.valid_to.map(|to| timestamp < to).unwrap_or(true)
}
pub fn is_gc_eligible(&self, cutoff_timestamp: u64) -> bool {
match self.valid_to {
Some(valid_to) => valid_to < cutoff_timestamp,
None => false,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct GCStats {
pub nodes_scanned: usize,
pub versions_deleted: usize,
pub bytes_freed: usize,
pub duration_ms: u64,
}
#[derive(Debug, Clone)]
pub struct GCConfig {
pub cutoff_timestamp: u64,
pub min_versions_to_keep: usize,
pub max_nodes_per_cycle: usize,
pub dry_run: bool,
pub use_active_horizon: bool,
}
impl Default for GCConfig {
fn default() -> Self {
Self {
cutoff_timestamp: 0,
min_versions_to_keep: 1,
max_nodes_per_cycle: 0,
dry_run: false,
use_active_horizon: false,
}
}
}
impl GCConfig {
pub fn older_than_ms(age_ms: u64) -> Self {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or(std::time::Duration::from_secs(0))
.as_millis() as u64;
Self {
cutoff_timestamp: now.saturating_sub(age_ms),
..Default::default()
}
}
pub fn older_than_hours(hours: u64) -> Self {
Self::older_than_ms(hours * 60 * 60 * 1000)
}
pub fn older_than_days(days: u64) -> Self {
Self::older_than_ms(days * 24 * 60 * 60 * 1000)
}
pub fn dry_run(mut self) -> Self {
self.dry_run = true;
self
}
pub fn with_active_horizon(mut self) -> Self {
self.use_active_horizon = true;
self
}
pub fn keep_at_least(mut self, n: usize) -> Self {
self.min_versions_to_keep = n;
self
}
}
pub struct VersionManager {
}
impl Default for VersionManager {
fn default() -> Self {
Self::new()
}
}
impl VersionManager {
pub fn new() -> Self {
Self {}
}
pub async fn get_version_at(
&self,
_node_id: NodeId,
_timestamp: u64,
) -> Result<Option<VersionedNode>> {
Ok(None)
}
pub async fn get_history(
&self,
_node_id: NodeId,
) -> Result<Vec<VersionedNode>> {
Ok(Vec::new())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::PropertyValue;
#[test]
fn test_versioned_node_creation() {
let node = Node::new("Person")
.with_property("name", PropertyValue::String("Alice".into()))
.with_property("age", PropertyValue::Int(25));
let v1 = VersionedNode::new(node, 100);
assert_eq!(v1.version, 1);
assert_eq!(v1.timestamp, 100);
assert_eq!(v1.valid_from, 100);
assert!(v1.valid_to.is_none());
assert!(v1.is_valid_at(100));
assert!(v1.is_valid_at(200));
}
#[test]
fn test_version_chain() {
let node1 = Node::new("Person")
.with_property("age", PropertyValue::Int(25));
let mut v1 = VersionedNode::new(node1, 100);
let node2 = Node::new("Person")
.with_property("age", PropertyValue::Int(30));
let v2 = VersionedNode::new_version(&v1, node2, 200);
v1.invalidate(200);
assert_eq!(v2.version, 2);
assert_eq!(v2.prev_version, Some(1));
assert!(v1.is_valid_at(150));
assert!(!v1.is_valid_at(200));
assert!(v2.is_valid_at(200));
}
#[test]
fn test_is_valid_at() {
let node = Node::new("Test");
let mut v = VersionedNode::new(node, 100);
assert!(!v.is_valid_at(50)); assert!(v.is_valid_at(100)); assert!(v.is_valid_at(150));
v.invalidate(200);
assert!(v.is_valid_at(150)); assert!(!v.is_valid_at(200)); assert!(!v.is_valid_at(250)); }
#[test]
fn test_gc_eligibility() {
let node = Node::new("Test");
let mut v = VersionedNode::new(node, 100);
assert!(!v.is_gc_eligible(500));
v.invalidate(200);
assert!(!v.is_gc_eligible(100)); assert!(!v.is_gc_eligible(200)); assert!(v.is_gc_eligible(201)); assert!(v.is_gc_eligible(500)); }
#[test]
fn test_gc_config() {
let config = GCConfig::older_than_hours(24);
assert!(config.cutoff_timestamp > 0);
assert_eq!(config.min_versions_to_keep, 1);
assert!(!config.dry_run);
let config = GCConfig::older_than_days(7).dry_run().keep_at_least(2);
assert!(config.dry_run);
assert_eq!(config.min_versions_to_keep, 2);
}
}