use crate::traits::{CreateDocumentDBOptions, StoreIndex};
use std::collections::HashMap;
use std::sync::{Arc, RwLock};
#[allow(dead_code)]
type Result<T> = std::result::Result<T, crate::guardian::error::GuardianError>;
#[derive(Clone)]
pub struct DocumentIndex {
index: Arc<RwLock<HashMap<String, Vec<u8>>>>,
#[allow(dead_code)]
opts: Arc<CreateDocumentDBOptions>,
}
impl DocumentIndex {
pub fn new(opts: Arc<CreateDocumentDBOptions>) -> Self {
Self {
index: Arc::new(RwLock::new(HashMap::new())),
opts,
}
}
pub fn keys(&self) -> Vec<String> {
let index_lock = self
.index
.read()
.expect("Failed to acquire read lock on document index");
index_lock.keys().cloned().collect()
}
pub fn get_bytes(&self, key: &str) -> Option<Vec<u8>> {
let index_lock = self
.index
.read()
.expect("Failed to acquire read lock on document index");
index_lock.get(key).cloned()
}
}
impl StoreIndex for DocumentIndex {
type Error = crate::guardian::error::GuardianError;
fn contains_key(&self, key: &str) -> std::result::Result<bool, Self::Error> {
let index_lock = self
.index
.read()
.expect("Failed to acquire read lock on document index");
Ok(index_lock.contains_key(key))
}
fn get_bytes(&self, key: &str) -> std::result::Result<Option<Vec<u8>>, Self::Error> {
let index_lock = self
.index
.read()
.expect("Failed to acquire read lock on document index");
Ok(index_lock.get(key).cloned())
}
fn keys(&self) -> std::result::Result<Vec<String>, Self::Error> {
let index_lock = self
.index
.read()
.expect("Failed to acquire read lock on document index");
Ok(index_lock.keys().cloned().collect())
}
fn len(&self) -> std::result::Result<usize, Self::Error> {
let index_lock = self
.index
.read()
.expect("Failed to acquire read lock on document index");
Ok(index_lock.len())
}
fn is_empty(&self) -> std::result::Result<bool, Self::Error> {
let index_lock = self
.index
.read()
.expect("Failed to acquire read lock on document index");
Ok(index_lock.is_empty())
}
fn update_index(
&mut self,
_log: &crate::log::Log,
entries: &[crate::log::entry::Entry],
) -> std::result::Result<(), Self::Error> {
let mut index = self
.index
.write()
.expect("Failed to acquire write lock on document index");
for entry in entries {
match crate::stores::operation::parse_operation(entry.clone()) {
Ok(operation) => {
match operation.op() {
"PUT" => {
if let Some(key) = operation.key()
&& !operation.value().is_empty()
{
index.insert(key.clone(), operation.value().to_vec());
}
}
"DEL" => {
if let Some(key) = operation.key() {
index.remove(key);
}
}
"PUTALL" => {
for doc in operation.docs() {
index.insert(doc.key().to_string(), doc.value().to_vec());
}
}
_ => {
}
}
}
Err(_) => {
continue;
}
}
}
Ok(())
}
fn clear(&mut self) -> std::result::Result<(), Self::Error> {
let mut index = self
.index
.write()
.expect("Failed to acquire write lock on document index");
index.clear();
Ok(())
}
}
impl DocumentIndex {
pub fn update_from_iroh_entries(
&mut self,
entries: Vec<(String, Vec<u8>)>,
) -> std::result::Result<(), crate::guardian::error::GuardianError> {
let mut index = self
.index
.write()
.expect("Failed to acquire write lock on document index");
index.clear();
for (key, hash_bytes) in entries {
if !hash_bytes.is_empty() {
index.insert(key, hash_bytes);
}
}
Ok(())
}
pub fn put(
&self,
key: String,
hash_bytes: Vec<u8>,
) -> std::result::Result<(), crate::guardian::error::GuardianError> {
let mut index = self
.index
.write()
.expect("Failed to acquire write lock on document index");
index.insert(key, hash_bytes);
Ok(())
}
pub fn remove(
&self,
key: &str,
) -> std::result::Result<Option<Vec<u8>>, crate::guardian::error::GuardianError> {
let mut index = self
.index
.write()
.expect("Failed to acquire write lock on document index");
Ok(index.remove(key))
}
pub fn stats(&self) -> IndexStats {
let index = self
.index
.read()
.expect("Failed to acquire read lock on document index");
let total_keys = index.len();
let total_bytes: usize = index.values().map(|v| v.len()).sum();
IndexStats {
total_keys,
total_bytes,
}
}
}
#[derive(Debug, Clone)]
pub struct IndexStats {
pub total_keys: usize,
pub total_bytes: usize,
}
#[cfg(test)]
mod tests {
use super::*;
fn create_test_opts() -> Arc<CreateDocumentDBOptions> {
Arc::new(CreateDocumentDBOptions {
marshal: Arc::new(|doc| {
serde_json::to_vec(doc)
.map_err(|e| crate::guardian::error::GuardianError::Other(e.to_string()))
}),
unmarshal: Arc::new(|bytes| {
serde_json::from_slice(bytes)
.map_err(|e| crate::guardian::error::GuardianError::Other(e.to_string()))
}),
key_extractor: Arc::new(|doc| {
Ok(doc
.get("_id")
.and_then(|v| v.as_str())
.unwrap_or("unknown")
.to_string())
}),
item_factory: Arc::new(|| {
serde_json::json!({
"_id": "",
"data": {}
})
}),
})
}
#[test]
fn test_new_index() {
let opts = create_test_opts();
let index = DocumentIndex::new(opts);
assert!(index.is_empty().unwrap());
}
#[test]
fn test_put_and_get() {
let opts = create_test_opts();
let index = DocumentIndex::new(opts);
let hash = vec![1u8; 32];
index.put("key1".to_string(), hash.clone()).unwrap();
let retrieved = index.get_bytes("key1");
assert_eq!(retrieved, Some(hash));
}
#[test]
fn test_remove() {
let opts = create_test_opts();
let index = DocumentIndex::new(opts);
let hash = vec![2u8; 32];
index.put("key2".to_string(), hash.clone()).unwrap();
let removed = index.remove("key2").unwrap();
assert_eq!(removed, Some(hash.clone()));
let retrieved = index.get_bytes("key2");
assert_eq!(retrieved, None);
}
#[test]
fn test_update_from_iroh_entries() {
let opts = create_test_opts();
let mut index = DocumentIndex::new(opts);
let entries = vec![
("key1".to_string(), vec![1u8; 32]),
("key2".to_string(), vec![2u8; 32]),
("key3".to_string(), vec![3u8; 32]),
];
index.update_from_iroh_entries(entries).unwrap();
assert_eq!(index.len().unwrap(), 3);
assert!(index.contains_key("key1").unwrap());
assert!(index.contains_key("key2").unwrap());
assert!(index.contains_key("key3").unwrap());
}
#[test]
fn test_stats() {
let opts = create_test_opts();
let index = DocumentIndex::new(opts);
index.put("key1".to_string(), vec![1u8; 32]).unwrap();
index.put("key2".to_string(), vec![2u8; 32]).unwrap();
let stats = index.stats();
assert_eq!(stats.total_keys, 2);
assert_eq!(stats.total_bytes, 64); }
#[test]
fn test_clear() {
let opts = create_test_opts();
let mut index = DocumentIndex::new(opts);
index.put("key1".to_string(), vec![1u8; 32]).unwrap();
index.put("key2".to_string(), vec![2u8; 32]).unwrap();
assert_eq!(index.len().unwrap(), 2);
index.clear().unwrap();
assert!(index.is_empty().unwrap());
}
}