use crate::core::error::Result;
use crate::core::hasher::IdentityHasher;
use crate::core::id::{EdgeId, NodeId, TxId, VersionId};
use crate::core::interning::InternedString;
use crate::core::property::{MAX_VECTOR_DIMENSIONS, PropertyKey, PropertyMap, PropertyValue};
use crate::core::temporal::{BiTemporalInterval, Timestamp};
use std::collections::{HashMap, HashSet};
use std::hash::BuildHasherDefault;
use std::sync::Arc;
pub type FastHashMap<K, V> = HashMap<K, V, BuildHasherDefault<IdentityHasher>>;
pub type FastHashSet<T> = HashSet<T, BuildHasherDefault<IdentityHasher>>;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct VersionMetadata {
pub created_by_tx: TxId,
pub commit_timestamp: Option<Timestamp>,
}
impl VersionMetadata {
pub fn new(created_by_tx: TxId, commit_timestamp: Timestamp) -> Self {
VersionMetadata {
created_by_tx,
commit_timestamp: Some(commit_timestamp),
}
}
pub fn uncommitted(created_by_tx: TxId) -> Self {
VersionMetadata {
created_by_tx,
commit_timestamp: None,
}
}
pub fn default_for_existing() -> Self {
use crate::core::hlc::HybridTimestamp;
VersionMetadata {
created_by_tx: TxId::new(0),
commit_timestamp: Some(HybridTimestamp::new_unchecked(0, 0)),
}
}
}
impl Default for VersionMetadata {
fn default() -> Self {
Self::default_for_existing()
}
}
const VECTOR_EPSILON: f32 = 1e-7;
fn floats_approx_equal(a: f32, b: f32) -> bool {
if a.is_nan() {
return b.is_nan();
}
if b.is_nan() {
return false;
}
if a.is_infinite() || b.is_infinite() {
return a == b;
}
(a - b).abs() <= VECTOR_EPSILON
}
#[derive(Debug, Clone)]
pub enum VectorDelta {
Sparse {
dimension: usize,
changes: Arc<Vec<(u32, f32)>>,
},
Full(Arc<[f32]>),
}
impl VectorDelta {
pub fn from_diff(old: &[f32], new: &[f32]) -> Option<Self> {
if old.len() != new.len() {
return None;
}
if old.len() > MAX_VECTOR_DIMENSIONS {
return None;
}
let mut changes = Vec::new();
for (idx, (old_val, new_val)) in old.iter().zip(new.iter()).enumerate() {
if !floats_approx_equal(*old_val, *new_val) {
let idx_u32 = u32::try_from(idx).ok()?;
changes.push((idx_u32, *new_val));
}
}
if changes.is_empty() {
return None;
}
let dimension = old.len();
if changes.len() * 2 < dimension {
Some(VectorDelta::Sparse {
dimension,
changes: Arc::new(changes),
})
} else {
Some(VectorDelta::Full(Arc::from(new)))
}
}
pub fn apply(&self, base: &[f32]) -> Vec<f32> {
match self {
VectorDelta::Sparse { dimension, changes } => {
if base.len() != *dimension {
debug_assert!(
base.len() == *dimension,
"VectorDelta applied to vector of wrong dimension. Expected: {}, Got: {}",
*dimension,
base.len()
);
return base.to_vec();
}
let mut result = base.to_vec();
for &(idx, value) in changes.iter() {
if (idx as usize) < result.len() {
result[idx as usize] = value;
}
}
result
}
VectorDelta::Full(new_vec) => new_vec.to_vec(),
}
}
pub fn estimated_heap_size(&self) -> usize {
match self {
VectorDelta::Sparse { changes, .. } => {
changes.capacity() * (std::mem::size_of::<u32>() + std::mem::size_of::<f32>())
}
VectorDelta::Full(vec) => vec.len() * std::mem::size_of::<f32>(),
}
}
}
impl PartialEq for VectorDelta {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(
VectorDelta::Sparse {
dimension: dim1,
changes: changes1,
},
VectorDelta::Sparse {
dimension: dim2,
changes: changes2,
},
) => {
if dim1 != dim2 {
return false;
}
if changes1.len() != changes2.len() {
return false;
}
for ((idx1, val1), (idx2, val2)) in changes1.iter().zip(changes2.iter()) {
if idx1 != idx2 || !floats_approx_equal(*val1, *val2) {
return false;
}
}
true
}
(VectorDelta::Full(vec1), VectorDelta::Full(vec2)) => {
if vec1.len() != vec2.len() {
return false;
}
for (v1, v2) in vec1.iter().zip(vec2.iter()) {
if !floats_approx_equal(*v1, *v2) {
return false;
}
}
true
}
_ => false, }
}
}
pub trait TemporalVersion {
fn temporal(&self) -> &BiTemporalInterval;
fn temporal_mut(&mut self) -> &mut BiTemporalInterval;
fn close_transaction_time(&mut self, end_timestamp: Timestamp) -> Result<()> {
let temporal = self.temporal_mut();
*temporal = temporal.close_transaction_time(end_timestamp)?;
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct AnchorConfig {
pub anchor_interval: u32,
pub max_delta_chain: u32,
}
impl Default for AnchorConfig {
fn default() -> Self {
AnchorConfig {
anchor_interval: 10,
max_delta_chain: 20,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct PropertyDelta {
pub changed: FastHashMap<PropertyKey, PropertyValue>,
pub vector_deltas: FastHashMap<PropertyKey, VectorDelta>,
pub removed: FastHashSet<PropertyKey>,
}
impl PropertyDelta {
pub fn new() -> Self {
PropertyDelta {
changed: FastHashMap::with_hasher(BuildHasherDefault::default()),
vector_deltas: FastHashMap::with_hasher(BuildHasherDefault::default()),
removed: FastHashSet::with_hasher(BuildHasherDefault::default()),
}
}
pub fn from_diff(old: &PropertyMap, new: &PropertyMap) -> Self {
if old == new {
return PropertyDelta::new();
}
let mut delta = PropertyDelta::new();
for (key, new_value) in new.iter() {
match old.get_by_interned_key(key) {
Some(old_value) => {
if old_value.semantically_equal(new_value) {
continue;
}
match (old_value.as_vector(), new_value.as_vector()) {
(Some(old_vec), Some(new_vec)) => {
if let Some(vec_delta) = VectorDelta::from_diff(old_vec, new_vec) {
delta.vector_deltas.insert(*key, vec_delta);
} else if old_vec.len() != new_vec.len() {
delta.changed.insert(*key, new_value.clone());
}
}
_ => {
delta.changed.insert(*key, new_value.clone());
}
}
}
None => {
delta.changed.insert(*key, new_value.clone());
}
}
}
for key in old.keys() {
if !new.contains_interned_key(key) {
delta.removed.insert(*key);
}
}
delta
}
pub fn apply(&self, base: &PropertyMap) -> PropertyMap {
if self.is_empty() {
return base.clone();
}
let estimated_capacity = base
.len()
.saturating_sub(self.removed.len())
.max(self.changed.len() + self.vector_deltas.len());
let mut result: FastHashMap<PropertyKey, PropertyValue> =
FastHashMap::with_capacity_and_hasher(
estimated_capacity,
BuildHasherDefault::<IdentityHasher>::default(),
);
for (key, value) in base.iter() {
if !self.removed.contains(key) {
result.insert(*key, value.clone());
}
}
for (key, value) in &self.changed {
result.insert(*key, value.clone());
}
for (key, vec_delta) in &self.vector_deltas {
match vec_delta {
VectorDelta::Full(vec) => {
result.insert(*key, PropertyValue::Vector(vec.clone()));
}
VectorDelta::Sparse { .. } => {
if let Some(base_value) = base.get_by_interned_key(key)
&& let Some(base_vec) = base_value.as_vector()
{
let new_vec = vec_delta.apply(base_vec);
result.insert(*key, new_vec.into());
}
}
}
}
result.into_iter().collect()
}
pub fn is_empty(&self) -> bool {
self.changed.is_empty() && self.vector_deltas.is_empty() && self.removed.is_empty()
}
pub fn materialize_vector_deltas(
&mut self,
base: &PropertyMap,
) -> std::result::Result<(), String> {
let keys: Vec<_> = self.vector_deltas.keys().copied().collect();
for key in keys {
if let Some(vec_delta) = self.vector_deltas.remove(&key) {
match vec_delta {
VectorDelta::Full(vec) => {
self.changed.insert(key, PropertyValue::Vector(vec));
}
VectorDelta::Sparse { .. } => {
let base_value = base.get_by_interned_key(&key).ok_or_else(|| {
format!(
"Cannot materialize sparse vector delta: base property not found for key {:?}",
key
)
})?;
let base_vec = base_value.as_vector().ok_or_else(|| {
format!(
"Cannot materialize sparse vector delta: base property is not a vector for key {:?}",
key
)
})?;
let new_vec = vec_delta.apply(base_vec);
self.changed.insert(key, new_vec.into());
}
}
}
}
Ok(())
}
pub fn estimated_heap_size(&self) -> usize {
let mut size = 0;
size += self.changed.capacity()
* (std::mem::size_of::<PropertyKey>() + std::mem::size_of::<PropertyValue>() + 8);
for value in self.changed.values() {
size += value.estimated_heap_size();
}
size += self.vector_deltas.capacity()
* (std::mem::size_of::<PropertyKey>() + std::mem::size_of::<VectorDelta>() + 8);
for vec_delta in self.vector_deltas.values() {
size += vec_delta.estimated_heap_size();
}
size += self.removed.capacity() * (std::mem::size_of::<PropertyKey>() + 8);
size
}
}
impl Default for PropertyDelta {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum VersionData {
Anchor {
properties: PropertyMap,
vector_snapshot_id: Option<usize>,
},
Delta {
delta: PropertyDelta,
},
}
impl VersionData {
pub fn anchor(properties: PropertyMap) -> Self {
VersionData::Anchor {
properties,
vector_snapshot_id: None,
}
}
pub fn delta_from_diff(old: &PropertyMap, new: &PropertyMap) -> Self {
VersionData::Delta {
delta: PropertyDelta::from_diff(old, new),
}
}
pub fn is_anchor(&self) -> bool {
matches!(self, VersionData::Anchor { .. })
}
pub fn is_delta(&self) -> bool {
matches!(self, VersionData::Delta { .. })
}
pub fn set_vector_snapshot_id(&mut self, id: usize) {
if let VersionData::Anchor {
vector_snapshot_id, ..
} = self
{
*vector_snapshot_id = Some(id);
}
}
pub fn get_vector_snapshot_id(&self) -> Option<usize> {
match self {
VersionData::Anchor {
vector_snapshot_id, ..
} => *vector_snapshot_id,
_ => None,
}
}
pub fn estimated_heap_size(&self) -> usize {
match self {
VersionData::Anchor { properties, .. } => properties.estimated_heap_size(),
VersionData::Delta { delta } => delta.estimated_heap_size(),
}
}
}
#[derive(Debug, Clone)]
pub struct NodeVersion {
pub id: VersionId,
pub node_id: NodeId,
pub temporal: BiTemporalInterval,
pub label: InternedString,
pub data: VersionData,
pub commit_timestamp: Timestamp,
pub next_version: Option<VersionId>,
pub prev_version: Option<VersionId>,
}
impl NodeVersion {
pub fn new_anchor(
id: VersionId,
node_id: NodeId,
temporal: BiTemporalInterval,
label: InternedString,
properties: PropertyMap,
) -> Self {
let commit_timestamp = temporal.transaction_time().start();
NodeVersion {
id,
node_id,
temporal,
label,
data: VersionData::anchor(properties),
commit_timestamp,
next_version: None,
prev_version: None,
}
}
pub fn new_delta(
id: VersionId,
node_id: NodeId,
temporal: BiTemporalInterval,
label: InternedString,
old_properties: &PropertyMap,
new_properties: &PropertyMap,
prev_version: VersionId,
) -> Self {
let commit_timestamp = temporal.transaction_time().start();
NodeVersion {
id,
node_id,
temporal,
label,
data: VersionData::delta_from_diff(old_properties, new_properties),
commit_timestamp,
next_version: None,
prev_version: Some(prev_version),
}
}
#[inline]
pub fn is_anchor(&self) -> bool {
self.data.is_anchor()
}
#[inline]
pub fn is_delta(&self) -> bool {
self.data.is_delta()
}
pub fn estimated_size(&self) -> usize {
std::mem::size_of::<Self>() + self.data.estimated_heap_size()
}
}
impl TemporalVersion for NodeVersion {
fn temporal(&self) -> &BiTemporalInterval {
&self.temporal
}
fn temporal_mut(&mut self) -> &mut BiTemporalInterval {
&mut self.temporal
}
}
#[derive(Debug, Clone)]
pub struct EdgeVersion {
pub id: VersionId,
pub edge_id: EdgeId,
pub temporal: BiTemporalInterval,
pub label: InternedString,
pub source: NodeId,
pub target: NodeId,
pub data: VersionData,
pub commit_timestamp: Timestamp,
pub next_version: Option<VersionId>,
pub prev_version: Option<VersionId>,
}
impl EdgeVersion {
pub fn new_anchor(
id: VersionId,
edge_id: EdgeId,
temporal: BiTemporalInterval,
label: InternedString,
source: NodeId,
target: NodeId,
properties: PropertyMap,
) -> Self {
let commit_timestamp = temporal.transaction_time().start();
EdgeVersion {
id,
edge_id,
temporal,
label,
source,
target,
data: VersionData::anchor(properties),
commit_timestamp,
next_version: None,
prev_version: None,
}
}
#[allow(clippy::too_many_arguments)]
pub fn new_delta(
id: VersionId,
edge_id: EdgeId,
temporal: BiTemporalInterval,
label: InternedString,
source: NodeId,
target: NodeId,
old_properties: &PropertyMap,
new_properties: &PropertyMap,
prev_version: VersionId,
) -> Self {
let commit_timestamp = temporal.transaction_time().start();
EdgeVersion {
id,
edge_id,
temporal,
label,
source,
target,
data: VersionData::delta_from_diff(old_properties, new_properties),
commit_timestamp,
next_version: None,
prev_version: Some(prev_version),
}
}
#[inline]
pub fn is_anchor(&self) -> bool {
self.data.is_anchor()
}
#[inline]
pub fn is_delta(&self) -> bool {
self.data.is_delta()
}
pub fn estimated_size(&self) -> usize {
std::mem::size_of::<Self>() + self.data.estimated_heap_size()
}
}
impl TemporalVersion for EdgeVersion {
fn temporal(&self) -> &BiTemporalInterval {
&self.temporal
}
fn temporal_mut(&mut self) -> &mut BiTemporalInterval {
&mut self.temporal
}
}
pub trait EntityVersion: TemporalVersion {
fn version_id(&self) -> VersionId;
fn is_anchor(&self) -> bool;
fn prev_version(&self) -> Option<VersionId>;
fn set_prev_version(&mut self, version_id: Option<VersionId>);
fn next_version(&self) -> Option<VersionId>;
fn set_next_version(&mut self, version_id: Option<VersionId>);
fn data_mut(&mut self) -> &mut VersionData;
}
impl EntityVersion for NodeVersion {
fn version_id(&self) -> VersionId {
self.id
}
fn is_anchor(&self) -> bool {
self.data.is_anchor()
}
fn prev_version(&self) -> Option<VersionId> {
self.prev_version
}
fn set_prev_version(&mut self, version_id: Option<VersionId>) {
self.prev_version = version_id;
}
fn next_version(&self) -> Option<VersionId> {
self.next_version
}
fn set_next_version(&mut self, version_id: Option<VersionId>) {
self.next_version = version_id;
}
fn data_mut(&mut self) -> &mut VersionData {
&mut self.data
}
}
impl EntityVersion for EdgeVersion {
fn version_id(&self) -> VersionId {
self.id
}
fn is_anchor(&self) -> bool {
self.data.is_anchor()
}
fn prev_version(&self) -> Option<VersionId> {
self.prev_version
}
fn set_prev_version(&mut self, version_id: Option<VersionId>) {
self.prev_version = version_id;
}
fn next_version(&self) -> Option<VersionId> {
self.next_version
}
fn set_next_version(&mut self, version_id: Option<VersionId>) {
self.next_version = version_id;
}
fn data_mut(&mut self) -> &mut VersionData {
&mut self.data
}
}
#[cfg(test)]
mod metadata_tests {
use super::*;
#[test]
fn test_version_metadata_new() {
let tx_id = TxId::new(100);
let timestamp = Timestamp::from(5000);
let metadata = VersionMetadata::new(tx_id, timestamp);
assert_eq!(metadata.created_by_tx, tx_id);
assert_eq!(metadata.commit_timestamp, Some(timestamp));
}
#[test]
fn test_version_metadata_uncommitted() {
let tx_id = TxId::new(200);
let metadata = VersionMetadata::uncommitted(tx_id);
assert_eq!(metadata.created_by_tx, tx_id);
assert_eq!(metadata.commit_timestamp, None);
}
#[test]
fn test_version_metadata_default() {
use std::process::Command;
use std::time::{Duration, Instant};
let exe = std::env::current_exe().expect("failed to locate current test binary");
let mut child = Command::new(exe)
.args([
"--ignored",
"--exact",
"core::version::metadata_tests::test_version_metadata_default_subprocess_helper",
])
.spawn()
.expect("failed to spawn subprocess for default metadata test");
let deadline = Instant::now() + Duration::from_secs(10);
loop {
match child.try_wait() {
Ok(Some(status)) => {
assert!(
status.success(),
"subprocess helper failed for VersionMetadata default semantics"
);
break;
}
Ok(None) => {
if Instant::now() >= deadline {
let _ = child.kill();
let _ = child.wait();
panic!("VersionMetadata::default/default_for_existing did not complete");
}
std::thread::sleep(Duration::from_millis(10));
}
Err(e) => panic!("failed while polling subprocess: {e}"),
}
}
}
#[test]
#[ignore]
fn test_version_metadata_default_subprocess_helper() {
let metadata = VersionMetadata::default();
let default_expected = VersionMetadata::default_for_existing();
assert_eq!(metadata.created_by_tx, default_expected.created_by_tx);
assert_eq!(metadata.commit_timestamp, default_expected.commit_timestamp);
assert_eq!(metadata.created_by_tx, TxId::new(0));
assert!(metadata.commit_timestamp.is_some());
}
#[test]
fn test_version_metadata_debug() {
let tx_id = TxId::new(123);
let timestamp = Timestamp::from(456);
let metadata = VersionMetadata::new(tx_id, timestamp);
let debug_str = format!("{:?}", metadata);
assert!(debug_str.contains("VersionMetadata"));
assert!(debug_str.contains("created_by_tx"));
assert!(debug_str.contains("commit_timestamp"));
assert!(debug_str.contains("123"));
}
#[test]
fn test_version_metadata_clone_copy() {
let tx_id = TxId::new(123);
let timestamp = Timestamp::from(456);
let metadata = VersionMetadata::new(tx_id, timestamp);
let copy = metadata; assert_eq!(metadata, copy);
#[allow(clippy::clone_on_copy)]
let clone = metadata.clone(); assert_eq!(metadata, clone);
}
}
#[cfg(test)]
mod storage_tests {
use super::*;
use crate::core::interning::GLOBAL_INTERNER;
use crate::core::property::PropertyMapBuilder;
#[test]
fn test_property_delta_diff() {
let old = PropertyMapBuilder::new()
.insert("name", "Alice")
.insert("age", 30i64)
.insert("city", "NYC")
.build();
let new = PropertyMapBuilder::new()
.insert("name", "Alice") .insert("age", 31i64) .insert("country", "USA") .build();
let delta = PropertyDelta::from_diff(&old, &new);
assert_eq!(delta.changed.len(), 2); assert_eq!(delta.removed.len(), 1); assert!(
delta
.removed
.contains(&GLOBAL_INTERNER.intern("city").unwrap())
);
}
#[test]
fn test_property_delta_apply() {
let base = PropertyMapBuilder::new()
.insert("name", "Alice")
.insert("age", 30i64)
.build();
let mut delta = PropertyDelta::new();
delta.changed.insert(
GLOBAL_INTERNER.intern("age").unwrap(),
PropertyValue::Int(31),
);
delta.changed.insert(
GLOBAL_INTERNER.intern("city").unwrap(),
PropertyValue::string("NYC"),
);
let result = delta.apply(&base);
assert_eq!(result.get("name").and_then(|v| v.as_str()), Some("Alice"));
assert_eq!(result.get("age").and_then(|v| v.as_int()), Some(31.into()));
assert_eq!(result.get("city").and_then(|v| v.as_str()), Some("NYC"));
}
#[test]
fn test_empty_delta() {
let props = PropertyMapBuilder::new().insert("name", "Alice").build();
let delta = PropertyDelta::from_diff(&props, &props);
assert!(delta.is_empty());
}
#[test]
fn test_node_version_anchor() {
let props = PropertyMapBuilder::new().insert("name", "Alice").build();
let temporal = BiTemporalInterval::current(1000.into());
let version = NodeVersion::new_anchor(
VersionId::new(1).unwrap(),
NodeId::new(10).unwrap(),
temporal,
crate::core::interning::GLOBAL_INTERNER
.intern("Person")
.unwrap(),
props,
);
assert!(version.is_anchor());
assert!(!version.is_delta());
assert_eq!(version.node_id, NodeId::new(10).unwrap());
}
#[test]
fn test_edge_version_delta() {
let old_props = PropertyMapBuilder::new().insert("weight", 1i64).build();
let new_props = PropertyMapBuilder::new().insert("weight", 2i64).build();
let temporal = BiTemporalInterval::current(2000.into());
let version = EdgeVersion::new_delta(
VersionId::new(2).unwrap(),
EdgeId::new(20).unwrap(),
temporal,
crate::core::interning::GLOBAL_INTERNER
.intern("KNOWS")
.unwrap(),
NodeId::new(1).unwrap(),
NodeId::new(2).unwrap(),
&old_props,
&new_props,
VersionId::new(1).unwrap(),
);
assert!(!version.is_anchor());
assert!(version.is_delta());
assert_eq!(version.prev_version, Some(VersionId::new(1).unwrap()));
}
#[test]
fn test_property_delta_estimated_heap_size_empty() {
let delta = PropertyDelta::new();
let size = delta.estimated_heap_size();
assert_eq!(size, 0, "Empty delta heap size should be zero");
}
#[test]
fn test_property_delta_estimated_heap_size_with_changes() {
let mut delta = PropertyDelta::new();
delta.changed.insert(
GLOBAL_INTERNER.intern("name").unwrap(),
PropertyValue::string("Alice"), );
delta.changed.insert(
GLOBAL_INTERNER.intern("description").unwrap(),
PropertyValue::string("A longer description"), );
delta
.removed
.insert(GLOBAL_INTERNER.intern("old_field").unwrap());
let size = delta.estimated_heap_size();
assert!(
size >= 25,
"Delta with strings should include string heap size"
);
}
#[test]
fn test_version_data_estimated_heap_size_anchor() {
let props = PropertyMapBuilder::new()
.insert("name", "Alice")
.insert("age", 30i64)
.build();
let data = VersionData::anchor(props);
let size = data.estimated_heap_size();
assert!(size >= 5, "Anchor heap size should include string 'Alice'");
}
#[test]
fn test_version_data_estimated_heap_size_delta() {
let old_props = PropertyMapBuilder::new().insert("name", "Alice").build();
let new_props = PropertyMapBuilder::new().insert("name", "Bob").build();
let data = VersionData::delta_from_diff(&old_props, &new_props);
let size = data.estimated_heap_size();
assert!(size >= 3, "Delta heap size should include string 'Bob'");
}
#[test]
fn test_node_version_estimated_size() {
let props = PropertyMapBuilder::new()
.insert("name", "Alice")
.insert("embedding", PropertyValue::vector(vec![0.1f32; 384]))
.build();
let temporal = BiTemporalInterval::current(1000.into());
let version = NodeVersion::new_anchor(
VersionId::new(1).unwrap(),
NodeId::new(10).unwrap(),
temporal,
GLOBAL_INTERNER.intern("Person").unwrap(),
props,
);
let size = version.estimated_size();
assert!(
size >= std::mem::size_of::<NodeVersion>() + 384 * 4,
"Node version estimated size should include vector heap"
);
}
#[test]
fn test_edge_version_estimated_size() {
let props = PropertyMapBuilder::new()
.insert("weight", 1.5f64)
.insert("label", "connection")
.build();
let temporal = BiTemporalInterval::current(1000.into());
let version = EdgeVersion::new_anchor(
VersionId::new(1).unwrap(),
EdgeId::new(20).unwrap(),
temporal,
GLOBAL_INTERNER.intern("CONNECTS").unwrap(),
NodeId::new(1).unwrap(),
NodeId::new(2).unwrap(),
props,
);
let size = version.estimated_size();
assert!(
size >= std::mem::size_of::<EdgeVersion>() + 10,
"Edge version estimated size should include string heap"
);
}
#[test]
fn test_node_version_estimated_size_delta() {
let old_props = PropertyMapBuilder::new().insert("count", 1i64).build();
let new_props = PropertyMapBuilder::new().insert("count", 2i64).build();
let temporal = BiTemporalInterval::current(2000.into());
let version = NodeVersion::new_delta(
VersionId::new(2).unwrap(),
NodeId::new(10).unwrap(),
temporal,
GLOBAL_INTERNER.intern("Counter").unwrap(),
&old_props,
&new_props,
VersionId::new(1).unwrap(),
);
let size = version.estimated_size();
assert!(
size >= std::mem::size_of::<NodeVersion>(),
"Delta version size should include at least stack size"
);
}
#[test]
fn test_vector_delta_sparse_optimization_single_element() {
let old_embedding = vec![0.1f32; 1536]; let mut new_embedding = old_embedding.clone();
new_embedding[500] = 0.2f32;
let old_props = PropertyMapBuilder::new()
.insert("embedding", PropertyValue::vector(&old_embedding))
.build();
let new_props = PropertyMapBuilder::new()
.insert("embedding", PropertyValue::vector(&new_embedding))
.build();
let delta = PropertyDelta::from_diff(&old_props, &new_props);
assert_eq!(
delta.changed.len(),
0,
"Vector should not be in changed (uses sparse delta)"
);
assert_eq!(
delta.vector_deltas.len(),
1,
"Vector should be in vector_deltas"
);
let delta_size = delta.estimated_heap_size();
let full_vector_size = 1536 * std::mem::size_of::<f32>();
assert!(
delta_size < full_vector_size / 10,
"Sparse delta ({} bytes) should be much smaller than full vector ({} bytes)",
delta_size,
full_vector_size
);
println!(
"OPTIMIZATION SUCCESS: Storing {} bytes (vs {} full) for 1-element change in 1536-dim vector ({}x savings)",
delta_size,
full_vector_size,
full_vector_size / delta_size.max(1)
);
}
#[test]
fn test_vector_delta_sparse_optimization_multiple_elements() {
let old_embedding = vec![0.1f32; 384];
let mut new_embedding = old_embedding.clone();
new_embedding[10] = 0.5f32;
new_embedding[50] = 0.6f32;
new_embedding[100] = 0.7f32;
new_embedding[200] = 0.8f32;
new_embedding[300] = 0.9f32;
let old_props = PropertyMapBuilder::new()
.insert("embedding", PropertyValue::vector(&old_embedding))
.build();
let new_props = PropertyMapBuilder::new()
.insert("embedding", PropertyValue::vector(&new_embedding))
.build();
let delta = PropertyDelta::from_diff(&old_props, &new_props);
assert_eq!(delta.vector_deltas.len(), 1, "Should have vector delta");
assert_eq!(delta.changed.len(), 0, "Should not store in changed");
let delta_size = delta.estimated_heap_size();
let full_vector_size = 384 * std::mem::size_of::<f32>();
assert!(
delta_size < full_vector_size / 4,
"Sparse delta ({} bytes) should be much smaller than full vector ({} bytes)",
delta_size,
full_vector_size
);
let optimal_sparse_size = 5 * (std::mem::size_of::<u32>() + std::mem::size_of::<f32>());
println!(
"OPTIMIZATION SUCCESS: {} bytes (vs {} full, {} raw sparse data) - {}x savings over full",
delta_size,
full_vector_size,
optimal_sparse_size,
full_vector_size / delta_size.max(1)
);
}
#[test]
fn test_vector_delta_no_change() {
let embedding = vec![0.1f32; 384];
let old_props = PropertyMapBuilder::new()
.insert("embedding", PropertyValue::vector(&embedding))
.build();
let new_props = PropertyMapBuilder::new()
.insert("embedding", PropertyValue::vector(&embedding))
.build();
let delta = PropertyDelta::from_diff(&old_props, &new_props);
assert!(
delta.is_empty(),
"Delta should be empty when vector is unchanged"
);
}
#[test]
fn test_vector_delta_complete_replacement() {
let old_embedding = vec![0.1f32; 384];
let new_embedding = vec![0.9f32; 384];
let old_props = PropertyMapBuilder::new()
.insert("embedding", PropertyValue::vector(&old_embedding))
.build();
let new_props = PropertyMapBuilder::new()
.insert("embedding", PropertyValue::vector(&new_embedding))
.build();
let delta = PropertyDelta::from_diff(&old_props, &new_props);
let delta_size = delta.estimated_heap_size();
let full_vector_size = 384 * std::mem::size_of::<f32>();
assert!(
delta_size >= full_vector_size,
"Full vector storage is expected for complete replacement"
);
}
#[test]
fn test_mixed_properties_with_vector_delta_optimization() {
let old_embedding = vec![0.1f32; 384];
let mut new_embedding = old_embedding.clone();
new_embedding[0] = 0.2f32;
let old_props = PropertyMapBuilder::new()
.insert("name", "Alice")
.insert("age", 30i64)
.insert("embedding", PropertyValue::vector(&old_embedding))
.build();
let new_props = PropertyMapBuilder::new()
.insert("name", "Alice") .insert("age", 31i64) .insert("embedding", PropertyValue::vector(&new_embedding)) .build();
let delta = PropertyDelta::from_diff(&old_props, &new_props);
assert_eq!(delta.changed.len(), 1, "Should have age changed");
assert_eq!(
delta.vector_deltas.len(),
1,
"Should have embedding in vector_deltas"
);
let delta_size = delta.estimated_heap_size();
let full_vector_size = 384 * std::mem::size_of::<f32>();
assert!(
delta_size < full_vector_size / 2,
"Mixed delta with sparse vector should be smaller than full vector"
);
println!(
"OPTIMIZATION: Mixed delta stores {} bytes (sparse vector + age property)",
delta_size
);
}
#[test]
fn test_sparse_vector_delta_single_element() {
let old_embedding = vec![0.1f32; 1536];
let mut new_embedding = old_embedding.clone();
new_embedding[500] = 0.2f32;
let old_props = PropertyMapBuilder::new()
.insert("embedding", PropertyValue::vector(&old_embedding))
.build();
let new_props = PropertyMapBuilder::new()
.insert("embedding", PropertyValue::vector(&new_embedding))
.build();
let delta = PropertyDelta::from_diff(&old_props, &new_props);
let sparse_data_size = std::mem::size_of::<u32>() + std::mem::size_of::<f32>();
let delta_size = delta.estimated_heap_size();
let full_vector_size = 1536 * std::mem::size_of::<f32>();
assert!(
delta_size < full_vector_size / 20,
"Sparse delta ({} bytes) should be much smaller than full vector ({} bytes). Raw data: {} bytes",
delta_size,
full_vector_size,
sparse_data_size
);
let result = delta.apply(&old_props);
assert_eq!(
result.get("embedding").and_then(|v| v.as_vector()),
new_props.get("embedding").and_then(|v| v.as_vector()),
"Applied delta should produce correct result"
);
}
#[test]
fn test_sparse_vector_delta_few_elements() {
let old_embedding = vec![0.1f32; 384];
let mut new_embedding = old_embedding.clone();
let changed_indices = vec![10, 50, 100, 150, 200, 250, 300, 350, 375, 383];
for &idx in &changed_indices {
new_embedding[idx] = 0.9f32;
}
let old_props = PropertyMapBuilder::new()
.insert("embedding", PropertyValue::vector(&old_embedding))
.build();
let new_props = PropertyMapBuilder::new()
.insert("embedding", PropertyValue::vector(&new_embedding))
.build();
let delta = PropertyDelta::from_diff(&old_props, &new_props);
let sparse_data_size = 10 * (std::mem::size_of::<u32>() + std::mem::size_of::<f32>());
let delta_size = delta.estimated_heap_size();
let full_vector_size = 384 * std::mem::size_of::<f32>();
assert!(
delta_size < full_vector_size / 4,
"Sparse delta ({} bytes) should be much smaller than full vector ({} bytes). Raw data: {} bytes",
delta_size,
full_vector_size,
sparse_data_size
);
let result = delta.apply(&old_props);
assert_eq!(
result.get("embedding").and_then(|v| v.as_vector()),
new_props.get("embedding").and_then(|v| v.as_vector())
);
}
#[test]
fn test_sparse_vector_delta_threshold_behavior() {
let old_embedding = vec![0.1f32; 384];
let mut new_embedding_sparse = old_embedding.clone();
for item in new_embedding_sparse.iter_mut().take(38) {
*item = 0.9f32;
}
let old_props = PropertyMapBuilder::new()
.insert("embedding", PropertyValue::vector(&old_embedding))
.build();
let sparse_props = PropertyMapBuilder::new()
.insert("embedding", PropertyValue::vector(&new_embedding_sparse))
.build();
let sparse_delta = PropertyDelta::from_diff(&old_props, &sparse_props);
let sparse_size = sparse_delta.estimated_heap_size();
let mut new_embedding_full = old_embedding.clone();
for item in new_embedding_full.iter_mut().take(346) {
*item = 0.9f32;
}
let full_props = PropertyMapBuilder::new()
.insert("embedding", PropertyValue::vector(&new_embedding_full))
.build();
let full_delta = PropertyDelta::from_diff(&old_props, &full_props);
let full_size = full_delta.estimated_heap_size();
assert!(
sparse_size < full_size / 2,
"Sparse delta ({} bytes) should be significantly smaller than full delta ({} bytes)",
sparse_size,
full_size
);
}
#[test]
fn test_sparse_vector_delta_edge_cases() {
let old_embedding = vec![0.1f32; 384];
let mut new_embedding = old_embedding.clone();
new_embedding[0] = 0.9f32;
let old_props = PropertyMapBuilder::new()
.insert("embedding", PropertyValue::vector(&old_embedding))
.build();
let new_props = PropertyMapBuilder::new()
.insert("embedding", PropertyValue::vector(&new_embedding))
.build();
let delta = PropertyDelta::from_diff(&old_props, &new_props);
let result = delta.apply(&old_props);
assert_eq!(
result.get("embedding").and_then(|v| v.as_vector()),
new_props.get("embedding").and_then(|v| v.as_vector()),
"First element change should work correctly"
);
let mut new_embedding_last = old_embedding.clone();
new_embedding_last[383] = 0.9f32;
let new_props_last = PropertyMapBuilder::new()
.insert("embedding", PropertyValue::vector(&new_embedding_last))
.build();
let delta_last = PropertyDelta::from_diff(&old_props, &new_props_last);
let result_last = delta_last.apply(&old_props);
assert_eq!(
result_last.get("embedding").and_then(|v| v.as_vector()),
new_props_last.get("embedding").and_then(|v| v.as_vector()),
"Last element change should work correctly"
);
}
#[test]
fn test_property_key_clone_is_cheap() {
let key1 = GLOBAL_INTERNER.intern("test_property").unwrap();
let key2 = key1;
assert_eq!(key1, key2);
let keys: Vec<_> = (0..1000)
.map(|i| GLOBAL_INTERNER.intern(format!("key_{}", i)).unwrap())
.collect();
let cloned_keys: Vec<_> = keys.to_vec();
for (original, cloned) in keys.iter().zip(cloned_keys.iter()) {
assert_eq!(original, cloned);
}
}
#[test]
fn test_property_value_clone_is_arc_refcount_increment() {
let string_val =
PropertyValue::string("A reasonably long string that would be expensive to deep copy");
let cloned_string = string_val.clone();
assert_eq!(string_val, cloned_string);
if let (PropertyValue::String(arc1), PropertyValue::String(arc2)) =
(&string_val, &cloned_string)
{
assert!(std::ptr::eq(
arc1.as_ref() as *const str,
arc2.as_ref() as *const str
));
} else {
panic!("Expected String variants");
}
let large_embedding = vec![0.1f32; 1536]; let vector_val = PropertyValue::vector(&large_embedding);
let cloned_vector = vector_val.clone();
assert_eq!(vector_val, cloned_vector);
if let (PropertyValue::Vector(arc1), PropertyValue::Vector(arc2)) =
(&vector_val, &cloned_vector)
{
assert!(std::ptr::eq(
arc1.as_ref() as *const [f32],
arc2.as_ref() as *const [f32]
));
} else {
panic!("Expected Vector variants");
}
let array_val = PropertyValue::array(vec![PropertyValue::Int(42); 100]);
let cloned_array = array_val.clone();
assert_eq!(array_val, cloned_array);
if let (PropertyValue::Array(arc1), PropertyValue::Array(arc2)) =
(&array_val, &cloned_array)
{
assert!(std::ptr::eq(
arc1.as_ref() as *const Vec<PropertyValue>,
arc2.as_ref() as *const Vec<PropertyValue>
));
} else {
panic!("Expected Array variants");
}
}
#[test]
fn test_property_delta_from_diff_clone_overhead() {
let old_props = PropertyMapBuilder::new()
.insert("name", "Alice")
.insert("age", 30i64)
.insert("city", "NYC")
.insert("embedding", PropertyValue::vector(vec![0.1f32; 384]))
.build();
let new_props = PropertyMapBuilder::new()
.insert("name", "Alice") .insert("age", 31i64) .insert("country", "USA") .insert("embedding", PropertyValue::vector(vec![0.2f32; 384])) .build();
let delta = PropertyDelta::from_diff(&old_props, &new_props);
assert_eq!(delta.changed.len(), 2); assert_eq!(delta.vector_deltas.len(), 1); assert_eq!(delta.removed.len(), 1);
let age_key = GLOBAL_INTERNER.intern("age").unwrap();
if let Some(delta_age) = delta.changed.get(&age_key)
&& let Some(new_age) = new_props.get("age")
{
assert_eq!(delta_age, new_age);
}
let embedding_key = GLOBAL_INTERNER.intern("embedding").unwrap();
assert!(delta.vector_deltas.contains_key(&embedding_key));
}
#[test]
fn test_property_delta_apply_clone_overhead() {
let base = PropertyMapBuilder::new()
.insert("name", "Alice")
.insert("age", 30i64)
.insert("city", "NYC")
.insert("embedding", PropertyValue::vector(vec![0.1f32; 384]))
.build();
let mut delta = PropertyDelta::new();
let age_key = GLOBAL_INTERNER.intern("age").unwrap();
let country_key = GLOBAL_INTERNER.intern("country").unwrap();
delta.changed.insert(age_key, PropertyValue::Int(31));
delta
.changed
.insert(country_key, PropertyValue::string("USA"));
let result = delta.apply(&base);
assert_eq!(result.get("name").and_then(|v| v.as_str()), Some("Alice"));
assert_eq!(result.get("age").and_then(|v| v.as_int()), Some(31.into()));
assert_eq!(result.get("country").and_then(|v| v.as_str()), Some("USA"));
assert_eq!(result.get("city").and_then(|v| v.as_str()), Some("NYC"));
if let (Some(base_name), Some(result_name)) = (base.get("name"), result.get("name"))
&& let (PropertyValue::String(base_arc), PropertyValue::String(result_arc)) =
(base_name, result_name)
{
assert!(std::ptr::eq(
base_arc.as_ref() as *const str,
result_arc.as_ref() as *const str
));
}
if let (Some(base_embedding), Some(result_embedding)) =
(base.get("embedding"), result.get("embedding"))
&& let (PropertyValue::Vector(base_arc), PropertyValue::Vector(result_arc)) =
(base_embedding, result_embedding)
{
assert!(std::ptr::eq(
base_arc.as_ref() as *const [f32],
result_arc.as_ref() as *const [f32]
));
}
}
#[test]
fn test_property_delta_apply_edge_cases() {
let empty_base = PropertyMapBuilder::new().build();
let mut delta = PropertyDelta::new();
delta.changed.insert(
GLOBAL_INTERNER.intern("new").unwrap(),
PropertyValue::Int(42),
);
let result = delta.apply(&empty_base);
assert_eq!(result.get("new").and_then(|v| v.as_int()), Some(42.into()));
let base = PropertyMapBuilder::new()
.insert("name", "Alice")
.insert("age", 30i64)
.build();
let empty_delta = PropertyDelta::new();
let result = empty_delta.apply(&base);
assert_eq!(result.get("name").and_then(|v| v.as_str()), Some("Alice"));
assert_eq!(result.get("age").and_then(|v| v.as_int()), Some(30.into()));
let base = PropertyMapBuilder::new()
.insert("name", "Alice")
.insert("age", 30i64)
.insert("city", "NYC")
.build();
let mut removal_delta = PropertyDelta::new();
removal_delta
.removed
.insert(GLOBAL_INTERNER.intern("city").unwrap());
let result = removal_delta.apply(&base);
assert_eq!(result.get("name").and_then(|v| v.as_str()), Some("Alice"));
assert_eq!(result.get("age").and_then(|v| v.as_int()), Some(30.into()));
assert!(result.get("city").is_none());
let mut large_base_builder = PropertyMapBuilder::new();
for i in 0..1000 {
large_base_builder = large_base_builder.insert(&format!("prop_{}", i), i as i64);
}
let large_base = large_base_builder.build();
let mut small_delta = PropertyDelta::new();
for i in 0..10 {
let key = GLOBAL_INTERNER.intern(format!("prop_{}", i)).unwrap();
small_delta
.changed
.insert(key, PropertyValue::Int((i + 1000) as i64));
}
let result = small_delta.apply(&large_base);
assert_eq!(
result.get("prop_0").and_then(|v| v.as_int()),
Some(1000.into())
);
assert_eq!(
result.get("prop_9").and_then(|v| v.as_int()),
Some(1009.into())
);
assert_eq!(
result.get("prop_10").and_then(|v| v.as_int()),
Some(10.into())
);
assert_eq!(
result.get("prop_999").and_then(|v| v.as_int()),
Some(999.into())
);
}
#[test]
fn test_sequential_delta_application_shares_arcs() {
let base = PropertyMapBuilder::new()
.insert("name", "Alice")
.insert("counter", 0i64)
.insert("embedding", PropertyValue::vector(vec![0.1f32; 384]))
.build();
let mut deltas = Vec::new();
for i in 1..=5 {
let mut delta = PropertyDelta::new();
let counter_key = GLOBAL_INTERNER.intern("counter").unwrap();
delta.changed.insert(counter_key, PropertyValue::Int(i));
deltas.push(delta);
}
let mut current = base.clone();
for delta in &deltas {
current = delta.apply(¤t);
}
assert_eq!(
current.get("counter").and_then(|v| v.as_int()),
Some(5.into())
);
if let (Some(base_name), Some(final_name)) = (base.get("name"), current.get("name"))
&& let (PropertyValue::String(base_arc), PropertyValue::String(final_arc)) =
(base_name, final_name)
{
assert!(std::ptr::eq(
base_arc.as_ref() as *const str,
final_arc.as_ref() as *const str
));
}
if let (Some(base_embedding), Some(final_embedding)) =
(base.get("embedding"), current.get("embedding"))
&& let (PropertyValue::Vector(base_arc), PropertyValue::Vector(final_arc)) =
(base_embedding, final_embedding)
{
assert!(std::ptr::eq(
base_arc.as_ref() as *const [f32],
final_arc.as_ref() as *const [f32]
));
}
}
}
#[cfg(test)]
mod sentry_tests {
use super::*;
use crate::core::interning::GLOBAL_INTERNER;
use crate::core::property::{MAX_VECTOR_DIMENSIONS, PropertyMapBuilder};
use std::sync::Arc;
#[test]
fn test_materialize_vector_deltas_missing_base_property() {
let key = GLOBAL_INTERNER.intern("embedding").unwrap();
let mut delta = PropertyDelta::new();
let sparse_delta = VectorDelta::Sparse {
dimension: 10,
changes: std::sync::Arc::new(vec![]),
};
delta.vector_deltas.insert(key, sparse_delta);
let base = PropertyMapBuilder::new().build();
let result = delta.materialize_vector_deltas(&base);
assert!(result.is_err());
let err = result.unwrap_err();
assert!(err.contains("base property not found"));
}
#[test]
fn test_materialize_vector_deltas_wrong_base_type() {
let key = GLOBAL_INTERNER.intern("embedding").unwrap();
let mut delta = PropertyDelta::new();
let sparse_delta = VectorDelta::Sparse {
dimension: 10,
changes: std::sync::Arc::new(vec![]),
};
delta.vector_deltas.insert(key, sparse_delta);
let base = PropertyMapBuilder::new().insert("embedding", 42i64).build();
let result = delta.materialize_vector_deltas(&base);
assert!(result.is_err());
let err = result.unwrap_err();
assert!(err.contains("base property is not a vector"));
}
#[test]
#[cfg(debug_assertions)]
#[should_panic(expected = "VectorDelta applied to vector of wrong dimension")]
fn test_vector_delta_apply_dimension_mismatch_panic() {
let delta = VectorDelta::Sparse {
dimension: 10,
changes: std::sync::Arc::new(vec![]),
};
let base = vec![0.0f32; 5];
let _ = delta.apply(&base);
}
#[test]
fn test_vector_delta_from_diff_max_dimensions() {
let len = MAX_VECTOR_DIMENSIONS + 1;
let v1 = vec![0.0f32; len];
let v2 = vec![1.0f32; len];
let result = VectorDelta::from_diff(&v1, &v2);
assert!(result.is_none());
}
#[test]
fn test_vector_delta_from_diff_nan_change() {
let old = vec![1.0f32];
let new = vec![f32::NAN];
let delta = VectorDelta::from_diff(&old, &new);
assert!(delta.is_some(), "Change from 1.0 to NaN should be detected");
let old = vec![f32::NAN];
let new = vec![1.0f32];
let delta = VectorDelta::from_diff(&old, &new);
assert!(delta.is_some(), "Change from NaN to 1.0 should be detected");
let old = vec![1.0f32];
let new = vec![f32::INFINITY];
let delta = VectorDelta::from_diff(&old, &new);
assert!(
delta.is_some(),
"Change from 1.0 to Infinity should be detected"
);
}
#[test]
fn test_vector_delta_apply_manual_construction_oob() {
let dimension = 10;
let changes = std::sync::Arc::new(vec![(100, 1.0f32)]); let delta = VectorDelta::Sparse { dimension, changes };
let base = vec![0.0f32; 10];
let result = delta.apply(&base);
assert_eq!(result.len(), 10);
assert_eq!(result[0], 0.0);
}
#[test]
fn test_property_delta_apply_full_vector_missing_base() {
let base = PropertyMapBuilder::new().build();
let mut delta = PropertyDelta::new();
let key = GLOBAL_INTERNER.intern("embedding").unwrap();
let new_vec = vec![1.0f32, 2.0, 3.0];
let vec_delta = VectorDelta::Full(Arc::from(new_vec.clone()));
delta.vector_deltas.insert(key, vec_delta);
let result = delta.apply(&base);
assert!(
result.get("embedding").is_some(),
"Full vector delta should be applied even if base property is missing"
);
if let Some(val) = result.get("embedding") {
assert_eq!(val.as_vector(), Some(new_vec.as_slice()));
}
}
#[test]
fn test_vector_delta_vs_property_value_equality() {
let v1 = vec![0.0f32];
let v2 = vec![VECTOR_EPSILON / 2.0];
let pv1 = PropertyValue::vector(&v1);
let pv2 = PropertyValue::vector(&v2);
assert_ne!(pv1, pv2, "PropertyValue should use exact equality");
let delta = VectorDelta::from_diff(&v1, &v2);
assert!(
delta.is_none(),
"VectorDelta should ignore changes smaller than epsilon"
);
let nan_vec = vec![f32::NAN];
let pv_nan1 = PropertyValue::vector(&nan_vec);
let pv_nan2 = PropertyValue::vector(&nan_vec);
assert_ne!(pv_nan1, pv_nan2, "PropertyValue should treat NaN != NaN");
let delta_nan = VectorDelta::from_diff(&nan_vec, &nan_vec);
assert!(
delta_nan.is_none(),
"VectorDelta should treat NaN as equal to NaN (no change)"
);
}
#[test]
fn test_property_delta_apply_sparse_ignored_on_missing_base() {
let mut delta = PropertyDelta::new();
let key = GLOBAL_INTERNER.intern("embedding").unwrap();
let changes = Arc::new(vec![(0, 1.0f32)]);
let vec_delta = VectorDelta::Sparse {
dimension: 10,
changes,
};
delta.vector_deltas.insert(key, vec_delta);
let base = PropertyMapBuilder::new().insert("name", "Alice").build();
let result = delta.apply(&base);
assert!(
result.get("embedding").is_none(),
"Sparse delta should be silently ignored if base property is missing"
);
}
#[test]
fn test_property_delta_apply_sparse_ignored_on_wrong_type() {
let mut delta = PropertyDelta::new();
let key = GLOBAL_INTERNER.intern("embedding").unwrap();
let changes = Arc::new(vec![(0, 1.0f32)]);
let vec_delta = VectorDelta::Sparse {
dimension: 10,
changes,
};
delta.vector_deltas.insert(key, vec_delta);
let base = PropertyMapBuilder::new().insert("embedding", 42i64).build();
let result = delta.apply(&base);
assert_eq!(
result.get("embedding").and_then(|v| v.as_int()),
Some(42),
"Sparse delta should be silently ignored if base property is wrong type"
);
}
#[test]
fn test_property_delta_silently_ignores_dimension_change() {
let old_vec = vec![1.0f32, 2.0];
let old_props = PropertyMapBuilder::new()
.insert("embedding", PropertyValue::vector(&old_vec))
.build();
let new_vec = vec![1.0f32, 2.0, 3.0];
let new_props = PropertyMapBuilder::new()
.insert("embedding", PropertyValue::vector(&new_vec))
.build();
let delta = PropertyDelta::from_diff(&old_props, &new_props);
let applied = delta.apply(&old_props);
let applied_vec = applied.get("embedding").unwrap().as_vector().unwrap();
assert_eq!(
applied_vec.len(),
3,
"PropertyDelta should apply full update on dimension change"
);
assert_eq!(applied_vec, &new_vec[..]);
}
#[test]
fn test_property_delta_handles_nan_no_change() {
let nan_val = PropertyValue::Float(f64::NAN);
let props = PropertyMapBuilder::new().insert("val", nan_val).build();
let delta = PropertyDelta::from_diff(&props, &props);
assert!(delta.is_empty(), "NaN -> NaN should result in empty delta");
}
#[test]
fn test_sentry_floats_epsilon_boundary() {
let old = vec![0.0f32];
let new = vec![VECTOR_EPSILON];
let delta = VectorDelta::from_diff(&old, &new);
assert!(
delta.is_none(),
"Difference of exactly EPSILON should be treated as equal"
);
}
#[test]
fn test_sentry_floats_infinite_equality() {
let old = vec![f32::INFINITY];
let new = vec![f32::INFINITY];
let delta = VectorDelta::from_diff(&old, &new);
assert!(delta.is_none(), "Infinity == Infinity should be no change");
}
#[test]
fn test_materialize_vector_deltas_success() {
let mut delta = PropertyDelta::new();
let key = GLOBAL_INTERNER.intern("embedding").unwrap();
let changes = std::sync::Arc::new(vec![(0, 1.0f32)]);
let vec_delta = VectorDelta::Sparse {
dimension: 10,
changes,
};
delta.vector_deltas.insert(key, vec_delta);
let base_vec = vec![0.0f32; 10];
let base = PropertyMapBuilder::new()
.insert_vector("embedding", &base_vec)
.build();
let result = delta.materialize_vector_deltas(&base);
assert!(result.is_ok(), "Materialization should succeed");
assert!(
delta.vector_deltas.is_empty(),
"vector_deltas should be empty after materialization"
);
assert!(
delta.changed.contains_key(&key),
"changed should contain the materialized vector"
);
let materialized = delta.changed.get(&key).unwrap();
if let PropertyValue::Vector(v) = materialized {
assert_eq!(v.len(), 10);
assert_eq!(v[0], 1.0f32); assert_eq!(v[1], 0.0f32); } else {
panic!("Materialized value should be a Vector");
}
}
#[test]
fn test_vector_delta_apply_empty_changes() {
let delta = VectorDelta::Sparse {
dimension: 5,
changes: Arc::new(vec![]),
};
let base = vec![1.0, 2.0, 3.0, 4.0, 5.0];
let result = delta.apply(&base);
assert_eq!(result, base);
}
#[test]
fn test_vector_delta_apply_out_of_bounds() {
let delta = VectorDelta::Sparse {
dimension: 3,
changes: Arc::new(vec![
(0, 10.0), (5, 20.0), ]),
};
let base = vec![1.0, 2.0, 3.0];
let result = delta.apply(&base);
assert_eq!(result.len(), 3);
assert_eq!(result[0], 10.0); assert_eq!(result[1], 2.0); assert_eq!(result[2], 3.0); }
}
#[cfg(test)]
mod mutant_kill_tests {
use super::*;
use crate::core::interning::GLOBAL_INTERNER;
use crate::core::property::PropertyMapBuilder;
use crate::core::temporal::TIMESTAMP_MAX;
use std::sync::Arc;
fn make_node_anchor() -> NodeVersion {
let props = PropertyMapBuilder::new().insert("name", "node").build();
NodeVersion::new_anchor(
VersionId::new(10).unwrap(),
NodeId::new(11).unwrap(),
BiTemporalInterval::current(1_000.into()),
GLOBAL_INTERNER.intern("Node").unwrap(),
props,
)
}
fn make_edge_anchor() -> EdgeVersion {
let props = PropertyMapBuilder::new().insert("weight", 1i64).build();
EdgeVersion::new_anchor(
VersionId::new(20).unwrap(),
EdgeId::new(21).unwrap(),
BiTemporalInterval::current(1_000.into()),
GLOBAL_INTERNER.intern("EDGE").unwrap(),
NodeId::new(1).unwrap(),
NodeId::new(2).unwrap(),
props,
)
}
fn make_edge_delta() -> EdgeVersion {
let old_props = PropertyMapBuilder::new().insert("weight", 1i64).build();
let new_props = PropertyMapBuilder::new().insert("weight", 2i64).build();
EdgeVersion::new_delta(
VersionId::new(22).unwrap(),
EdgeId::new(23).unwrap(),
BiTemporalInterval::current(2_000.into()),
GLOBAL_INTERNER.intern("EDGE").unwrap(),
NodeId::new(1).unwrap(),
NodeId::new(2).unwrap(),
&old_props,
&new_props,
VersionId::new(20).unwrap(),
)
}
#[test]
fn test_vector_delta_from_diff_allows_exact_max_dimensions() {
let len = MAX_VECTOR_DIMENSIONS;
let old = vec![0.0f32; len];
let mut new = old.clone();
new[0] = 1.0;
let delta = VectorDelta::from_diff(&old, &new);
assert!(delta.is_some(), "Max dimension should be allowed");
}
#[test]
fn test_vector_delta_from_diff_threshold_behavior_for_sparse_vs_full() {
let old_full = vec![0.0f32; 4];
let mut new_full = old_full.clone();
new_full[0] = 1.0;
new_full[1] = 2.0;
let delta_full = VectorDelta::from_diff(&old_full, &new_full).unwrap();
assert!(
matches!(delta_full, VectorDelta::Full(_)),
"Threshold boundary should choose full storage"
);
let old_sparse = vec![0.0f32; 3];
let mut new_sparse = old_sparse.clone();
new_sparse[0] = 1.0;
let delta_sparse = VectorDelta::from_diff(&old_sparse, &new_sparse).unwrap();
assert!(
matches!(delta_sparse, VectorDelta::Sparse { .. }),
"Few changes should choose sparse storage"
);
}
#[test]
fn test_vector_delta_apply_ignores_index_equal_to_length() {
let base = vec![0.0f32, 1.0, 2.0];
let delta = VectorDelta::Sparse {
dimension: 3,
changes: Arc::new(vec![(3, 99.0)]),
};
let result = delta.apply(&base);
assert_eq!(result, base);
}
#[test]
fn test_vector_delta_sparse_estimated_heap_size_matches_formula() {
let mut changes = Vec::with_capacity(4);
changes.push((0u32, 1.0f32));
changes.push((3u32, 2.0f32));
let delta = VectorDelta::Sparse {
dimension: 8,
changes: Arc::new(changes),
};
let expected = match &delta {
VectorDelta::Sparse { changes, .. } => {
changes.capacity() * (std::mem::size_of::<u32>() + std::mem::size_of::<f32>())
}
_ => unreachable!(),
};
assert_eq!(delta.estimated_heap_size(), expected);
}
#[test]
fn test_vector_delta_partial_eq_semantics() {
let sparse_a = VectorDelta::Sparse {
dimension: 4,
changes: Arc::new(vec![(1, 0.5), (3, 1.0)]),
};
let sparse_b = VectorDelta::Sparse {
dimension: 4,
changes: Arc::new(vec![(1, 0.5), (3, 1.0)]),
};
assert_eq!(sparse_a, sparse_b);
let sparse_dim_mismatch = VectorDelta::Sparse {
dimension: 5,
changes: Arc::new(vec![(1, 0.5), (3, 1.0)]),
};
assert_ne!(sparse_a, sparse_dim_mismatch);
let sparse_len_mismatch = VectorDelta::Sparse {
dimension: 4,
changes: Arc::new(vec![(1, 0.5)]),
};
assert_ne!(sparse_a, sparse_len_mismatch);
let sparse_idx_mismatch = VectorDelta::Sparse {
dimension: 4,
changes: Arc::new(vec![(0, 0.5), (3, 1.0)]),
};
assert_ne!(sparse_a, sparse_idx_mismatch);
let sparse_val_mismatch = VectorDelta::Sparse {
dimension: 4,
changes: Arc::new(vec![(1, 0.5 + 1e-3), (3, 1.0)]),
};
assert_ne!(sparse_a, sparse_val_mismatch);
let full_a = VectorDelta::Full(Arc::from(vec![1.0f32, 2.0f32]));
let full_b = VectorDelta::Full(Arc::from(vec![1.0f32, 2.0f32]));
assert_eq!(full_a, full_b);
let full_len_mismatch = VectorDelta::Full(Arc::from(vec![1.0f32]));
assert_ne!(full_a, full_len_mismatch);
let full_val_mismatch = VectorDelta::Full(Arc::from(vec![1.0f32, 2.5f32]));
assert_ne!(full_a, full_val_mismatch);
assert_ne!(sparse_a, full_a);
}
#[test]
fn test_temporal_version_close_transaction_time_updates_tx_dimension() {
let mut node = make_node_anchor();
let end = Timestamp::from(2_000);
node.close_transaction_time(end).unwrap();
assert_eq!(node.temporal().transaction_time().end(), end);
assert_eq!(node.temporal().valid_time().end(), TIMESTAMP_MAX);
}
#[test]
fn test_property_delta_is_empty_only_when_all_collections_empty() {
let key_a = GLOBAL_INTERNER.intern("a").unwrap();
let key_v = GLOBAL_INTERNER.intern("v").unwrap();
let key_r = GLOBAL_INTERNER.intern("r").unwrap();
let mut changed_only = PropertyDelta::new();
changed_only.changed.insert(key_a, PropertyValue::Int(1));
assert!(!changed_only.is_empty());
let mut vector_only = PropertyDelta::new();
vector_only.vector_deltas.insert(
key_v,
VectorDelta::Sparse {
dimension: 2,
changes: Arc::new(vec![(0, 1.0)]),
},
);
assert!(!vector_only.is_empty());
let mut removed_only = PropertyDelta::new();
removed_only.removed.insert(key_r);
assert!(!removed_only.is_empty());
assert!(PropertyDelta::new().is_empty());
}
#[test]
fn test_property_delta_estimated_heap_size_matches_formula() {
let mut delta = PropertyDelta::new();
let key_name = GLOBAL_INTERNER.intern("name").unwrap();
let key_vec = GLOBAL_INTERNER.intern("embedding").unwrap();
let key_removed = GLOBAL_INTERNER.intern("old").unwrap();
delta
.changed
.insert(key_name, PropertyValue::string("Alice"));
delta.vector_deltas.insert(
key_vec,
VectorDelta::Sparse {
dimension: 4,
changes: Arc::new(vec![(1, 2.0)]),
},
);
delta.removed.insert(key_removed);
let expected_changed_overhead = delta.changed.capacity()
* (std::mem::size_of::<PropertyKey>() + std::mem::size_of::<PropertyValue>() + 8);
let expected_changed_values: usize = delta
.changed
.values()
.map(PropertyValue::estimated_heap_size)
.sum();
let expected_vector_overhead = delta.vector_deltas.capacity()
* (std::mem::size_of::<PropertyKey>() + std::mem::size_of::<VectorDelta>() + 8);
let expected_vector_values: usize = delta
.vector_deltas
.values()
.map(VectorDelta::estimated_heap_size)
.sum();
let expected_removed_overhead =
delta.removed.capacity() * (std::mem::size_of::<PropertyKey>() + 8);
let expected = expected_changed_overhead
+ expected_changed_values
+ expected_vector_overhead
+ expected_vector_values
+ expected_removed_overhead;
assert_eq!(delta.estimated_heap_size(), expected);
}
#[test]
fn test_node_and_edge_estimated_size_match_formula() {
let node = make_node_anchor();
assert_eq!(
node.estimated_size(),
std::mem::size_of::<NodeVersion>() + node.data.estimated_heap_size()
);
let edge = make_edge_anchor();
assert_eq!(
edge.estimated_size(),
std::mem::size_of::<EdgeVersion>() + edge.data.estimated_heap_size()
);
}
#[test]
fn test_edge_anchor_reports_not_delta() {
let edge = make_edge_anchor();
assert!(!edge.is_delta());
}
#[test]
fn test_entity_version_trait_round_trip_links_for_node_and_edge() {
fn set_links<V: EntityVersion>(
v: &mut V,
prev: Option<VersionId>,
next: Option<VersionId>,
) {
v.set_prev_version(prev);
v.set_next_version(next);
}
fn links<V: EntityVersion>(v: &V) -> (Option<VersionId>, Option<VersionId>) {
(v.prev_version(), v.next_version())
}
let mut node = make_node_anchor();
let node_prev = Some(VersionId::new(99).unwrap());
let node_next = Some(VersionId::new(100).unwrap());
set_links(&mut node, node_prev, node_next);
assert_eq!(links(&node), (node_prev, node_next));
set_links(&mut node, None, None);
assert_eq!(links(&node), (None, None));
let mut edge = make_edge_anchor();
let edge_prev = Some(VersionId::new(199).unwrap());
let edge_next = Some(VersionId::new(200).unwrap());
set_links(&mut edge, edge_prev, edge_next);
assert_eq!(links(&edge), (edge_prev, edge_next));
set_links(&mut edge, None, None);
assert_eq!(links(&edge), (None, None));
}
#[test]
fn test_entity_version_trait_is_anchor_for_edge_variants() {
fn trait_is_anchor<V: EntityVersion>(v: &V) -> bool {
v.is_anchor()
}
let edge_anchor = make_edge_anchor();
let edge_delta = make_edge_delta();
assert!(trait_is_anchor(&edge_anchor));
assert!(!trait_is_anchor(&edge_delta));
}
#[test]
fn test_vector_delta_epsilon_boundary() {
const SMALL_OFFSET: f32 = 1e-9;
let v1 = vec![0.0f32];
let v2 = vec![VECTOR_EPSILON];
assert!(
VectorDelta::from_diff(&v1, &v2).is_none(),
"Difference of exactly EPSILON should be considered equal (no delta)"
);
let v3 = vec![VECTOR_EPSILON + SMALL_OFFSET];
assert!(
VectorDelta::from_diff(&v1, &v3).is_some(),
"Difference > EPSILON should be detected"
);
let v4 = vec![VECTOR_EPSILON - SMALL_OFFSET];
assert!(
VectorDelta::from_diff(&v1, &v4).is_none(),
"Difference < EPSILON should be considered equal"
);
}
}
#[cfg(test)]
mod embedded_commit_timestamp_tests {
use super::*;
use crate::core::interning::GLOBAL_INTERNER;
use crate::core::property::PropertyMapBuilder;
#[test]
fn test_node_version_anchor_has_embedded_commit_timestamp() {
let commit_ts: Timestamp = 500.into();
let temporal = BiTemporalInterval::now(100.into(), commit_ts);
let version = NodeVersion::new_anchor(
VersionId::new(1).unwrap(),
NodeId::new(1).unwrap(),
temporal,
GLOBAL_INTERNER.intern("TestLabel").unwrap(),
PropertyMapBuilder::new().build(),
);
assert_eq!(
version.commit_timestamp, commit_ts,
"NodeVersion must embed commit_timestamp equal to transaction_time start"
);
}
#[test]
fn test_node_version_delta_has_embedded_commit_timestamp() {
let first_ts: Timestamp = 100.into();
let second_ts: Timestamp = 200.into();
let node_id = NodeId::new(1).unwrap();
let label = GLOBAL_INTERNER.intern("TestLabel").unwrap();
let old_props = PropertyMapBuilder::new().insert("x", 1i64).build();
let new_props = PropertyMapBuilder::new().insert("x", 2i64).build();
let delta = NodeVersion::new_delta(
VersionId::new(2).unwrap(),
node_id,
BiTemporalInterval::now(50.into(), second_ts),
label,
&old_props,
&new_props,
VersionId::new(1).unwrap(),
);
assert_eq!(
delta.commit_timestamp, second_ts,
"NodeVersion delta must embed commit_timestamp from transaction_time start"
);
let anchor = NodeVersion::new_anchor(
VersionId::new(1).unwrap(),
node_id,
BiTemporalInterval::now(50.into(), first_ts),
label,
old_props,
);
assert_eq!(anchor.commit_timestamp, first_ts);
}
#[test]
fn test_edge_version_anchor_has_embedded_commit_timestamp() {
let commit_ts: Timestamp = 750.into();
let temporal = BiTemporalInterval::now(200.into(), commit_ts);
let version = EdgeVersion::new_anchor(
VersionId::new(10).unwrap(),
EdgeId::new(5).unwrap(),
temporal,
GLOBAL_INTERNER.intern("KNOWS").unwrap(),
NodeId::new(1).unwrap(),
NodeId::new(2).unwrap(),
PropertyMapBuilder::new().build(),
);
assert_eq!(
version.commit_timestamp, commit_ts,
"EdgeVersion must embed commit_timestamp equal to transaction_time start"
);
}
#[test]
fn test_edge_version_delta_has_embedded_commit_timestamp() {
let commit_ts: Timestamp = 900.into();
let edge_id = EdgeId::new(5).unwrap();
let label = GLOBAL_INTERNER.intern("KNOWS").unwrap();
let old_props = PropertyMapBuilder::new().insert("w", 1i64).build();
let new_props = PropertyMapBuilder::new().insert("w", 2i64).build();
let delta = EdgeVersion::new_delta(
VersionId::new(11).unwrap(),
edge_id,
BiTemporalInterval::now(200.into(), commit_ts),
label,
NodeId::new(1).unwrap(),
NodeId::new(2).unwrap(),
&old_props,
&new_props,
VersionId::new(10).unwrap(),
);
assert_eq!(
delta.commit_timestamp, commit_ts,
"EdgeVersion delta must embed commit_timestamp from transaction_time start"
);
}
#[test]
fn test_commit_timestamp_enables_direct_visibility_check() {
let commit_ts: Timestamp = 50.into();
let snapshot_ts: Timestamp = 100.into();
let version = NodeVersion::new_anchor(
VersionId::new(1).unwrap(),
NodeId::new(1).unwrap(),
BiTemporalInterval::now(10.into(), commit_ts),
GLOBAL_INTERNER.intern("N").unwrap(),
PropertyMapBuilder::new().build(),
);
assert!(version.commit_timestamp < snapshot_ts);
let late_version = NodeVersion::new_anchor(
VersionId::new(2).unwrap(),
NodeId::new(1).unwrap(),
BiTemporalInterval::now(10.into(), 150.into()),
GLOBAL_INTERNER.intern("N").unwrap(),
PropertyMapBuilder::new().build(),
);
assert!(late_version.commit_timestamp > snapshot_ts);
}
}