use crate::error::Result;
use crate::types::{MemoryRecord, RecordId};
use parking_lot::RwLock;
use std::fmt::Debug;
use std::sync::Arc;
pub trait RecordStore: Send + Sync + Debug {
fn insert(&mut self, record: MemoryRecord) -> Result<RecordId>;
fn insert_batch(&mut self, records: Vec<MemoryRecord>) -> Result<Vec<RecordId>> {
let mut ids = Vec::with_capacity(records.len());
for record in records {
ids.push(self.insert(record)?);
}
Ok(ids)
}
fn get(&self, id: &RecordId) -> Option<MemoryRecord>;
fn get_batch(&self, ids: &[RecordId]) -> Vec<Option<MemoryRecord>> {
ids.iter().map(|id| self.get(id)).collect()
}
fn contains(&self, id: &RecordId) -> bool;
fn update_stats(&mut self, id: &RecordId, outcome: f64) -> Result<()>;
fn remove(&mut self, id: &RecordId) -> Result<bool>;
fn len(&self) -> usize;
fn is_empty(&self) -> bool {
self.len() == 0
}
fn clear(&mut self);
fn ids(&self) -> Vec<RecordId>;
fn memory_usage(&self) -> usize;
}
pub type SharedStore<S> = Arc<RwLock<S>>;
#[allow(dead_code)]
pub fn shared_store<S: RecordStore>(store: S) -> SharedStore<S> {
Arc::new(RwLock::new(store))
}
#[cfg(test)]
pub mod tests {
use super::*;
use crate::OutcomeStats;
pub fn test_basic_crud<S: RecordStore>(store: &mut S) {
use crate::types::RecordStatus;
let record = MemoryRecord {
id: "test-1".into(),
embedding: vec![1.0, 2.0, 3.0],
context: "test context".into(),
outcome: 0.8,
metadata: Default::default(),
created_at: 0,
status: RecordStatus::Active,
stats: OutcomeStats::new(1),
};
let id = store.insert(record.clone()).unwrap();
assert_eq!(id.as_str(), "test-1");
let retrieved = store.get(&id).unwrap();
assert_eq!(retrieved.id.as_str(), "test-1");
assert!((retrieved.outcome - 0.8).abs() < 0.001);
assert!(store.contains(&id));
assert!(!store.contains(&"nonexistent".into()));
assert_eq!(store.len(), 1);
store.update_stats(&id, 0.9).unwrap();
store.update_stats(&id, 0.7).unwrap();
let updated = store.get(&id).unwrap();
assert_eq!(updated.stats.count(), 2);
assert!(store.remove(&id).unwrap());
assert!(!store.contains(&id));
assert_eq!(store.len(), 0);
}
pub fn test_batch_operations<S: RecordStore>(store: &mut S) {
use crate::types::RecordStatus;
let records: Vec<MemoryRecord> = (0..10)
.map(|i| MemoryRecord {
id: format!("batch-{i}").into(),
embedding: vec![i as f32; 3],
context: format!("context {i}"),
outcome: i as f64 / 10.0,
metadata: Default::default(),
created_at: i as u64,
status: RecordStatus::Active,
stats: OutcomeStats::new(1),
})
.collect();
let ids = store.insert_batch(records).unwrap();
assert_eq!(ids.len(), 10);
assert_eq!(store.len(), 10);
let retrieved = store.get_batch(&ids[..3]);
assert_eq!(retrieved.len(), 3);
assert!(retrieved.iter().all(|r| r.is_some()));
let all_ids = store.ids();
assert_eq!(all_ids.len(), 10);
store.clear();
assert!(store.is_empty());
}
pub fn test_duplicate_insert<S: RecordStore>(store: &mut S) {
use crate::types::RecordStatus;
let record = MemoryRecord {
id: "dup-test".into(),
embedding: vec![1.0],
context: "test".into(),
outcome: 0.5,
metadata: Default::default(),
created_at: 0,
status: RecordStatus::Active,
stats: OutcomeStats::new(1),
};
store.insert(record.clone()).unwrap();
let result = store.insert(record);
assert!(result.is_err());
}
}