use common::{NamespaceId, Vector, VectorId};
use parking_lot::RwLock;
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, VecDeque};
use std::sync::atomic::{AtomicU64, Ordering};
#[derive(Debug, Clone)]
pub struct DeltaConfig {
pub max_delta_chain: usize,
pub sparse_threshold: f32,
pub epsilon: f32,
pub enable_rle: bool,
pub max_memory_per_namespace: usize,
pub auto_compact: bool,
pub compact_threshold: usize,
}
impl Default for DeltaConfig {
fn default() -> Self {
Self {
max_delta_chain: 10,
sparse_threshold: 0.5, epsilon: 1e-7,
enable_rle: true,
max_memory_per_namespace: 100 * 1024 * 1024, auto_compact: true,
compact_threshold: 5,
}
}
}
impl DeltaConfig {
pub fn new() -> Self {
Self::default()
}
pub fn with_max_chain(mut self, max: usize) -> Self {
self.max_delta_chain = max;
self
}
pub fn with_sparse_threshold(mut self, threshold: f32) -> Self {
self.sparse_threshold = threshold.clamp(0.0, 1.0);
self
}
pub fn with_epsilon(mut self, epsilon: f32) -> Self {
self.epsilon = epsilon.abs();
self
}
pub fn without_rle(mut self) -> Self {
self.enable_rle = false;
self
}
pub fn without_auto_compact(mut self) -> Self {
self.auto_compact = false;
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ComponentChange {
pub index: u32,
pub value: f32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RleChange {
pub start: u32,
pub values: Vec<f32>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum DeltaEncoding {
Sparse(Vec<ComponentChange>),
Rle(Vec<RleChange>),
Full(Vec<f32>),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VectorDelta {
pub version: u64,
pub base_version: u64,
pub timestamp: u64,
pub encoding: DeltaEncoding,
pub size_bytes: usize,
}
impl VectorDelta {
pub fn change_count(&self) -> usize {
match &self.encoding {
DeltaEncoding::Sparse(changes) => changes.len(),
DeltaEncoding::Rle(runs) => runs.iter().map(|r| r.values.len()).sum(),
DeltaEncoding::Full(values) => values.len(),
}
}
pub fn is_full(&self) -> bool {
matches!(self.encoding, DeltaEncoding::Full(_))
}
}
#[derive(Debug, Clone)]
pub struct VersionedVector {
pub id: VectorId,
pub current_version: u64,
pub base_snapshot: Vec<f32>,
pub base_version: u64,
pub deltas: VecDeque<VectorDelta>,
pub metadata: Option<serde_json::Value>,
pub ttl_seconds: Option<u64>,
pub expires_at: Option<u64>,
}
impl VersionedVector {
pub fn new(vector: &Vector) -> Self {
Self {
id: vector.id.clone(),
current_version: 1,
base_snapshot: vector.values.clone(),
base_version: 1,
deltas: VecDeque::new(),
metadata: vector.metadata.clone(),
ttl_seconds: vector.ttl_seconds,
expires_at: vector.expires_at,
}
}
pub fn reconstruct(&self) -> Vec<f32> {
let mut values = self.base_snapshot.clone();
for delta in &self.deltas {
apply_delta(&mut values, delta);
}
values
}
pub fn reconstruct_at_version(&self, version: u64) -> Option<Vec<f32>> {
if version < self.base_version {
return None; }
if version == self.base_version {
return Some(self.base_snapshot.clone());
}
let mut values = self.base_snapshot.clone();
for delta in &self.deltas {
if delta.version > version {
break;
}
apply_delta(&mut values, delta);
}
Some(values)
}
pub fn size_bytes(&self) -> usize {
let base_size = self.base_snapshot.len() * 4;
let delta_size: usize = self.deltas.iter().map(|d| d.size_bytes).sum();
let metadata_size = self
.metadata
.as_ref()
.map(|m| serde_json::to_string(m).map(|s| s.len()).unwrap_or(0))
.unwrap_or(0);
base_size + delta_size + metadata_size + 64 }
pub fn to_vector(&self) -> Vector {
Vector {
id: self.id.clone(),
values: self.reconstruct(),
metadata: self.metadata.clone(),
ttl_seconds: self.ttl_seconds,
expires_at: self.expires_at,
}
}
pub fn delta_chain_length(&self) -> usize {
self.deltas.len()
}
}
fn apply_delta(values: &mut Vec<f32>, delta: &VectorDelta) {
match &delta.encoding {
DeltaEncoding::Sparse(changes) => {
for change in changes {
let idx = change.index as usize;
if idx >= values.len() {
values.resize(idx + 1, 0.0);
}
values[idx] = change.value;
}
}
DeltaEncoding::Rle(runs) => {
for run in runs {
let start = run.start as usize;
for (i, &value) in run.values.iter().enumerate() {
let idx = start + i;
if idx >= values.len() {
values.resize(idx + 1, 0.0);
}
values[idx] = value;
}
}
}
DeltaEncoding::Full(new_values) => {
values.clear();
values.extend_from_slice(new_values);
}
}
}
pub fn compute_delta(
old_values: &[f32],
new_values: &[f32],
config: &DeltaConfig,
base_version: u64,
new_version: u64,
) -> VectorDelta {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs();
if old_values.len() != new_values.len() {
let size_bytes = estimate_encoding_size(&DeltaEncoding::Full(new_values.to_vec()));
return VectorDelta {
version: new_version,
base_version,
timestamp: now,
encoding: DeltaEncoding::Full(new_values.to_vec()),
size_bytes,
};
}
let mut changes: Vec<(usize, f32)> = Vec::new();
let len = old_values.len();
for i in 0..len {
if (old_values[i] - new_values[i]).abs() > config.epsilon {
changes.push((i, new_values[i]));
}
}
let change_ratio = changes.len() as f32 / new_values.len().max(1) as f32;
let encoding = if change_ratio > config.sparse_threshold {
DeltaEncoding::Full(new_values.to_vec())
} else if config.enable_rle && changes.len() > 2 {
encode_rle(&changes)
} else {
DeltaEncoding::Sparse(
changes
.into_iter()
.map(|(idx, val)| ComponentChange {
index: idx as u32,
value: val,
})
.collect(),
)
};
let size_bytes = estimate_encoding_size(&encoding);
VectorDelta {
version: new_version,
base_version,
timestamp: now,
encoding,
size_bytes,
}
}
fn encode_rle(changes: &[(usize, f32)]) -> DeltaEncoding {
if changes.is_empty() {
return DeltaEncoding::Sparse(vec![]);
}
let mut runs: Vec<RleChange> = Vec::new();
let mut current_run: Option<RleChange> = None;
for &(idx, value) in changes {
match &mut current_run {
Some(run) => {
let expected_idx = run.start as usize + run.values.len();
if idx == expected_idx {
run.values.push(value);
} else {
runs.push(current_run.take().unwrap());
current_run = Some(RleChange {
start: idx as u32,
values: vec![value],
});
}
}
None => {
current_run = Some(RleChange {
start: idx as u32,
values: vec![value],
});
}
}
}
if let Some(run) = current_run {
runs.push(run);
}
let rle_size: usize = runs.iter().map(|r| 4 + r.values.len() * 4).sum();
let sparse_size = changes.len() * 8;
if rle_size < sparse_size {
DeltaEncoding::Rle(runs)
} else {
DeltaEncoding::Sparse(
changes
.iter()
.map(|&(idx, val)| ComponentChange {
index: idx as u32,
value: val,
})
.collect(),
)
}
}
fn estimate_encoding_size(encoding: &DeltaEncoding) -> usize {
match encoding {
DeltaEncoding::Sparse(changes) => changes.len() * 8 + 16,
DeltaEncoding::Rle(runs) => runs.iter().map(|r| 8 + r.values.len() * 4).sum::<usize>() + 16,
DeltaEncoding::Full(values) => values.len() * 4 + 16,
}
}
#[derive(Debug, Clone, Default)]
pub struct DeltaStats {
pub total_vectors: u64,
pub total_deltas: u64,
pub total_snapshots: u64,
pub memory_bytes: u64,
pub avg_chain_length: f64,
pub compression_ratio: f64,
pub compactions: u64,
}
pub struct NamespaceDeltaStore {
vectors: RwLock<HashMap<VectorId, VersionedVector>>,
config: DeltaConfig,
stats: AtomicDeltaStats,
}
struct AtomicDeltaStats {
total_vectors: AtomicU64,
total_deltas: AtomicU64,
compactions: AtomicU64,
}
impl AtomicDeltaStats {
fn new() -> Self {
Self {
total_vectors: AtomicU64::new(0),
total_deltas: AtomicU64::new(0),
compactions: AtomicU64::new(0),
}
}
}
impl NamespaceDeltaStore {
pub fn new(config: DeltaConfig) -> Self {
Self {
vectors: RwLock::new(HashMap::new()),
config,
stats: AtomicDeltaStats::new(),
}
}
pub fn upsert(&self, vector: &Vector) -> UpsertResult {
let mut vectors = self.vectors.write();
if let Some(existing) = vectors.get_mut(&vector.id) {
let current_values = existing.reconstruct();
let new_version = existing.current_version + 1;
let delta = compute_delta(
¤t_values,
&vector.values,
&self.config,
existing.current_version,
new_version,
);
let delta_size = delta.size_bytes;
let is_full = delta.is_full();
existing.deltas.push_back(delta);
existing.current_version = new_version;
existing.metadata = vector.metadata.clone();
existing.ttl_seconds = vector.ttl_seconds;
existing.expires_at = vector.expires_at;
self.stats.total_deltas.fetch_add(1, Ordering::SeqCst);
let should_compact = self.config.auto_compact
&& existing.delta_chain_length() >= self.config.compact_threshold;
if should_compact {
self.compact_vector(existing);
}
UpsertResult {
is_new: false,
version: new_version,
delta_size,
used_full_encoding: is_full,
compacted: should_compact,
}
} else {
let versioned = VersionedVector::new(vector);
vectors.insert(vector.id.clone(), versioned);
self.stats.total_vectors.fetch_add(1, Ordering::SeqCst);
UpsertResult {
is_new: true,
version: 1,
delta_size: 0,
used_full_encoding: false,
compacted: false,
}
}
}
pub fn get(&self, id: &VectorId) -> Option<Vector> {
self.vectors.read().get(id).map(|v| v.to_vector())
}
pub fn get_at_version(&self, id: &VectorId, version: u64) -> Option<Vector> {
let vectors = self.vectors.read();
let versioned = vectors.get(id)?;
let values = versioned.reconstruct_at_version(version)?;
Some(Vector {
id: versioned.id.clone(),
values,
metadata: versioned.metadata.clone(),
ttl_seconds: versioned.ttl_seconds,
expires_at: versioned.expires_at,
})
}
pub fn get_version_info(&self, id: &VectorId) -> Option<VersionInfo> {
let vectors = self.vectors.read();
let versioned = vectors.get(id)?;
Some(VersionInfo {
id: versioned.id.clone(),
current_version: versioned.current_version,
base_version: versioned.base_version,
delta_count: versioned.deltas.len(),
size_bytes: versioned.size_bytes(),
})
}
pub fn delete(&self, id: &VectorId) -> bool {
let removed = self.vectors.write().remove(id).is_some();
if removed {
self.stats.total_vectors.fetch_sub(1, Ordering::SeqCst);
}
removed
}
pub fn get_all(&self) -> Vec<Vector> {
self.vectors
.read()
.values()
.map(|v| v.to_vector())
.collect()
}
fn compact_vector(&self, versioned: &mut VersionedVector) {
let current_values = versioned.reconstruct();
versioned.base_snapshot = current_values;
versioned.base_version = versioned.current_version;
versioned.deltas.clear();
self.stats.compactions.fetch_add(1, Ordering::SeqCst);
}
pub fn compact(&self, id: &VectorId) -> bool {
let mut vectors = self.vectors.write();
if let Some(versioned) = vectors.get_mut(id) {
self.compact_vector(versioned);
true
} else {
false
}
}
pub fn compact_all(&self) -> usize {
let mut vectors = self.vectors.write();
let mut count = 0;
for versioned in vectors.values_mut() {
if !versioned.deltas.is_empty() {
self.compact_vector(versioned);
count += 1;
}
}
count
}
pub fn stats(&self) -> DeltaStats {
let vectors = self.vectors.read();
let total_vectors = vectors.len() as u64;
let total_deltas: usize = vectors.values().map(|v| v.deltas.len()).sum();
let memory_bytes: usize = vectors.values().map(|v| v.size_bytes()).sum();
let avg_chain = if total_vectors > 0 {
total_deltas as f64 / total_vectors as f64
} else {
0.0
};
let original_size: usize = vectors
.values()
.map(|v| v.reconstruct().len() * 4 * (v.deltas.len() + 1))
.sum();
let compression_ratio = if memory_bytes > 0 {
original_size as f64 / memory_bytes as f64
} else {
1.0
};
DeltaStats {
total_vectors,
total_deltas: total_deltas as u64,
total_snapshots: total_vectors,
memory_bytes: memory_bytes as u64,
avg_chain_length: avg_chain,
compression_ratio,
compactions: self.stats.compactions.load(Ordering::SeqCst),
}
}
pub fn count(&self) -> usize {
self.vectors.read().len()
}
pub fn is_empty(&self) -> bool {
self.vectors.read().is_empty()
}
pub fn clear(&self) {
self.vectors.write().clear();
self.stats.total_vectors.store(0, Ordering::SeqCst);
self.stats.total_deltas.store(0, Ordering::SeqCst);
}
}
#[derive(Debug, Clone)]
pub struct UpsertResult {
pub is_new: bool,
pub version: u64,
pub delta_size: usize,
pub used_full_encoding: bool,
pub compacted: bool,
}
#[derive(Debug, Clone)]
pub struct VersionInfo {
pub id: VectorId,
pub current_version: u64,
pub base_version: u64,
pub delta_count: usize,
pub size_bytes: usize,
}
pub struct DeltaStoreManager {
stores: RwLock<HashMap<NamespaceId, NamespaceDeltaStore>>,
config: DeltaConfig,
}
impl DeltaStoreManager {
pub fn new(config: DeltaConfig) -> Self {
Self {
stores: RwLock::new(HashMap::new()),
config,
}
}
pub fn with_defaults() -> Self {
Self::new(DeltaConfig::default())
}
pub fn get_or_create(&self, namespace: &NamespaceId) -> bool {
let mut stores = self.stores.write();
if !stores.contains_key(namespace) {
stores.insert(
namespace.clone(),
NamespaceDeltaStore::new(self.config.clone()),
);
true
} else {
false
}
}
pub fn upsert(&self, namespace: &NamespaceId, vectors: &[Vector]) -> Vec<UpsertResult> {
self.get_or_create(namespace);
let stores = self.stores.read();
if let Some(store) = stores.get(namespace) {
vectors.iter().map(|v| store.upsert(v)).collect()
} else {
vec![]
}
}
pub fn get(&self, namespace: &NamespaceId, id: &VectorId) -> Option<Vector> {
self.stores.read().get(namespace)?.get(id)
}
pub fn get_all(&self, namespace: &NamespaceId) -> Vec<Vector> {
self.stores
.read()
.get(namespace)
.map(|s| s.get_all())
.unwrap_or_default()
}
pub fn delete(&self, namespace: &NamespaceId, id: &VectorId) -> bool {
self.stores
.read()
.get(namespace)
.map(|s| s.delete(id))
.unwrap_or(false)
}
pub fn delete_namespace(&self, namespace: &NamespaceId) -> bool {
self.stores.write().remove(namespace).is_some()
}
pub fn compact_namespace(&self, namespace: &NamespaceId) -> usize {
self.stores
.read()
.get(namespace)
.map(|s| s.compact_all())
.unwrap_or(0)
}
pub fn namespace_stats(&self, namespace: &NamespaceId) -> Option<DeltaStats> {
self.stores.read().get(namespace).map(|s| s.stats())
}
pub fn stats(&self) -> DeltaStats {
let stores = self.stores.read();
let mut combined = DeltaStats::default();
for store in stores.values() {
let s = store.stats();
combined.total_vectors += s.total_vectors;
combined.total_deltas += s.total_deltas;
combined.total_snapshots += s.total_snapshots;
combined.memory_bytes += s.memory_bytes;
combined.compactions += s.compactions;
}
if combined.total_vectors > 0 {
combined.avg_chain_length =
combined.total_deltas as f64 / combined.total_vectors as f64;
}
combined
}
pub fn list_namespaces(&self) -> Vec<NamespaceId> {
self.stores.read().keys().cloned().collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_vector(id: &str, values: Vec<f32>) -> Vector {
Vector {
id: id.to_string(),
values,
metadata: None,
ttl_seconds: None,
expires_at: None,
}
}
#[test]
fn test_delta_config_builder() {
let config = DeltaConfig::new()
.with_max_chain(20)
.with_sparse_threshold(0.3)
.with_epsilon(1e-5)
.without_rle()
.without_auto_compact();
assert_eq!(config.max_delta_chain, 20);
assert!((config.sparse_threshold - 0.3).abs() < 0.001);
assert!((config.epsilon - 1e-5).abs() < 1e-10);
assert!(!config.enable_rle);
assert!(!config.auto_compact);
}
#[test]
fn test_compute_delta_sparse() {
let old = vec![1.0, 2.0, 3.0, 4.0, 5.0];
let new = vec![1.0, 2.5, 3.0, 4.0, 5.5];
let config = DeltaConfig::default();
let delta = compute_delta(&old, &new, &config, 1, 2);
assert_eq!(delta.version, 2);
assert_eq!(delta.base_version, 1);
if let DeltaEncoding::Sparse(changes) = delta.encoding {
assert_eq!(changes.len(), 2);
assert!(changes
.iter()
.any(|c| c.index == 1 && (c.value - 2.5).abs() < 0.001));
assert!(changes
.iter()
.any(|c| c.index == 4 && (c.value - 5.5).abs() < 0.001));
} else {
panic!("Expected sparse encoding");
}
}
#[test]
fn test_compute_delta_rle() {
let old = vec![0.0; 10];
let new = vec![0.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0];
let config = DeltaConfig::default();
let delta = compute_delta(&old, &new, &config, 1, 2);
if let DeltaEncoding::Rle(runs) = delta.encoding {
assert_eq!(runs.len(), 1);
assert_eq!(runs[0].start, 1);
assert_eq!(runs[0].values.len(), 4);
} else if let DeltaEncoding::Sparse(changes) = delta.encoding {
assert_eq!(changes.len(), 4);
} else {
panic!("Expected RLE or Sparse encoding");
}
}
#[test]
fn test_compute_delta_full() {
let old = vec![0.0; 10];
let new = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0];
let config = DeltaConfig::default();
let delta = compute_delta(&old, &new, &config, 1, 2);
assert!(delta.is_full());
if let DeltaEncoding::Full(values) = delta.encoding {
assert_eq!(values, new);
} else {
panic!("Expected full encoding");
}
}
#[test]
fn test_apply_delta_sparse() {
let mut values = vec![1.0, 2.0, 3.0, 4.0, 5.0];
let delta = VectorDelta {
version: 2,
base_version: 1,
timestamp: 0,
encoding: DeltaEncoding::Sparse(vec![
ComponentChange {
index: 1,
value: 10.0,
},
ComponentChange {
index: 3,
value: 20.0,
},
]),
size_bytes: 0,
};
apply_delta(&mut values, &delta);
assert_eq!(values, vec![1.0, 10.0, 3.0, 20.0, 5.0]);
}
#[test]
fn test_apply_delta_rle() {
let mut values = vec![0.0; 10];
let delta = VectorDelta {
version: 2,
base_version: 1,
timestamp: 0,
encoding: DeltaEncoding::Rle(vec![RleChange {
start: 2,
values: vec![1.0, 2.0, 3.0],
}]),
size_bytes: 0,
};
apply_delta(&mut values, &delta);
assert_eq!(values[2], 1.0);
assert_eq!(values[3], 2.0);
assert_eq!(values[4], 3.0);
}
#[test]
fn test_versioned_vector() {
let v = make_vector("v1", vec![1.0, 2.0, 3.0]);
let versioned = VersionedVector::new(&v);
assert_eq!(versioned.current_version, 1);
assert_eq!(versioned.base_version, 1);
assert_eq!(versioned.deltas.len(), 0);
assert_eq!(versioned.reconstruct(), vec![1.0, 2.0, 3.0]);
}
#[test]
fn test_namespace_delta_store_upsert_new() {
let store = NamespaceDeltaStore::new(DeltaConfig::default());
let v = make_vector("v1", vec![1.0, 2.0, 3.0]);
let result = store.upsert(&v);
assert!(result.is_new);
assert_eq!(result.version, 1);
assert_eq!(result.delta_size, 0);
}
#[test]
fn test_namespace_delta_store_upsert_update() {
let store = NamespaceDeltaStore::new(DeltaConfig::default());
let v1 = make_vector("v1", vec![1.0, 2.0, 3.0]);
store.upsert(&v1);
let v2 = make_vector("v1", vec![1.0, 5.0, 3.0]);
let result = store.upsert(&v2);
assert!(!result.is_new);
assert_eq!(result.version, 2);
assert!(result.delta_size > 0);
let retrieved = store.get(&"v1".to_string()).unwrap();
assert_eq!(retrieved.values, vec![1.0, 5.0, 3.0]);
}
#[test]
fn test_namespace_delta_store_version_history() {
let store = NamespaceDeltaStore::new(DeltaConfig::default().without_auto_compact());
store.upsert(&make_vector("v1", vec![1.0, 2.0, 3.0])); store.upsert(&make_vector("v1", vec![1.0, 5.0, 3.0])); store.upsert(&make_vector("v1", vec![1.0, 5.0, 10.0]));
let current = store.get(&"v1".to_string()).unwrap();
assert_eq!(current.values, vec![1.0, 5.0, 10.0]);
let v2 = store.get_at_version(&"v1".to_string(), 2).unwrap();
assert_eq!(v2.values, vec![1.0, 5.0, 3.0]);
let v1 = store.get_at_version(&"v1".to_string(), 1).unwrap();
assert_eq!(v1.values, vec![1.0, 2.0, 3.0]);
}
#[test]
fn test_namespace_delta_store_compact() {
let store = NamespaceDeltaStore::new(DeltaConfig::default().without_auto_compact());
store.upsert(&make_vector("v1", vec![1.0, 2.0, 3.0]));
store.upsert(&make_vector("v1", vec![1.0, 5.0, 3.0]));
store.upsert(&make_vector("v1", vec![1.0, 5.0, 10.0]));
let info_before = store.get_version_info(&"v1".to_string()).unwrap();
assert_eq!(info_before.delta_count, 2);
store.compact(&"v1".to_string());
let info_after = store.get_version_info(&"v1".to_string()).unwrap();
assert_eq!(info_after.delta_count, 0);
let v = store.get(&"v1".to_string()).unwrap();
assert_eq!(v.values, vec![1.0, 5.0, 10.0]);
}
#[test]
fn test_namespace_delta_store_auto_compact() {
let config = DeltaConfig::default()
.with_max_chain(10)
.with_sparse_threshold(0.9);
let store = NamespaceDeltaStore::new(DeltaConfig {
compact_threshold: 3,
auto_compact: true,
..config
});
store.upsert(&make_vector("v1", vec![1.0, 2.0, 3.0]));
store.upsert(&make_vector("v1", vec![1.1, 2.0, 3.0])); store.upsert(&make_vector("v1", vec![1.2, 2.0, 3.0])); let result = store.upsert(&make_vector("v1", vec![1.3, 2.0, 3.0]));
assert!(result.compacted);
let info = store.get_version_info(&"v1".to_string()).unwrap();
assert_eq!(info.delta_count, 0);
}
#[test]
fn test_namespace_delta_store_delete() {
let store = NamespaceDeltaStore::new(DeltaConfig::default());
store.upsert(&make_vector("v1", vec![1.0, 2.0, 3.0]));
assert!(store.delete(&"v1".to_string()));
assert!(store.get(&"v1".to_string()).is_none());
assert!(!store.delete(&"v1".to_string())); }
#[test]
fn test_namespace_delta_store_stats() {
let store = NamespaceDeltaStore::new(DeltaConfig::default().without_auto_compact());
store.upsert(&make_vector("v1", vec![1.0, 2.0, 3.0]));
store.upsert(&make_vector("v1", vec![1.0, 5.0, 3.0]));
store.upsert(&make_vector("v2", vec![4.0, 5.0, 6.0]));
let stats = store.stats();
assert_eq!(stats.total_vectors, 2);
assert_eq!(stats.total_deltas, 1);
assert!(stats.memory_bytes > 0);
}
#[test]
fn test_delta_store_manager() {
let manager = DeltaStoreManager::with_defaults();
let results = manager.upsert(
&"ns1".to_string(),
&[make_vector("v1", vec![1.0, 2.0, 3.0])],
);
assert_eq!(results.len(), 1);
assert!(results[0].is_new);
let v = manager.get(&"ns1".to_string(), &"v1".to_string()).unwrap();
assert_eq!(v.values, vec![1.0, 2.0, 3.0]);
let namespaces = manager.list_namespaces();
assert!(namespaces.contains(&"ns1".to_string()));
assert!(manager.delete_namespace(&"ns1".to_string()));
assert!(manager.get(&"ns1".to_string(), &"v1".to_string()).is_none());
}
#[test]
fn test_encode_rle_efficiency() {
let changes: Vec<(usize, f32)> = (0..10).map(|i| (i, i as f32)).collect();
let encoding = encode_rle(&changes);
if let DeltaEncoding::Rle(runs) = encoding {
assert_eq!(runs.len(), 1);
assert_eq!(runs[0].values.len(), 10);
} else {
}
}
#[test]
fn test_dimension_change() {
let store = NamespaceDeltaStore::new(DeltaConfig::default());
store.upsert(&make_vector("v1", vec![1.0, 2.0, 3.0]));
store.upsert(&make_vector("v1", vec![1.0, 2.0, 3.0, 4.0, 5.0]));
let v = store.get(&"v1".to_string()).unwrap();
assert_eq!(v.values, vec![1.0, 2.0, 3.0, 4.0, 5.0]);
}
#[test]
fn test_epsilon_change_detection() {
let config = DeltaConfig::default().with_epsilon(0.1);
let store = NamespaceDeltaStore::new(config);
store.upsert(&make_vector("v1", vec![1.0, 2.0, 3.0]));
store.upsert(&make_vector("v1", vec![1.05, 2.0, 3.0]));
let info = store.get_version_info(&"v1".to_string()).unwrap();
assert!(info.current_version >= 1);
}
}