use common::{DakeraError, Vector};
use storage::{InMemoryStorage, ObjectStorage, VectorStorage};
fn test_vector(id: &str, dim: usize) -> Vector {
Vector {
id: id.to_string(),
values: (0..dim).map(|i| i as f32).collect(),
metadata: None,
ttl_seconds: None,
expires_at: None,
}
}
fn test_vector_with_metadata(id: &str, dim: usize, meta: serde_json::Value) -> Vector {
Vector {
id: id.to_string(),
values: (0..dim).map(|i| i as f32).collect(),
metadata: Some(meta),
ttl_seconds: None,
expires_at: None,
}
}
async fn test_basic_crud<S: VectorStorage>(storage: &S, namespace: &str) {
let namespace = namespace.to_string();
storage.ensure_namespace(&namespace).await.unwrap();
assert!(storage.namespace_exists(&namespace).await.unwrap());
assert_eq!(storage.count(&namespace).await.unwrap(), 0);
let v1 = test_vector("vec_1", 4);
let count = storage.upsert(&namespace, vec![v1.clone()]).await.unwrap();
assert_eq!(count, 1);
assert_eq!(storage.count(&namespace).await.unwrap(), 1);
let ids = vec!["vec_1".to_string()];
let retrieved = storage.get(&namespace, &ids).await.unwrap();
assert_eq!(retrieved.len(), 1);
assert_eq!(retrieved[0].id, "vec_1");
assert_eq!(retrieved[0].values, v1.values);
let v1_updated = Vector {
id: "vec_1".to_string(),
values: vec![10.0, 20.0, 30.0, 40.0],
metadata: None,
ttl_seconds: None,
expires_at: None,
};
let count = storage
.upsert(&namespace, vec![v1_updated.clone()])
.await
.unwrap();
assert_eq!(count, 1);
assert_eq!(storage.count(&namespace).await.unwrap(), 1);
let ids = vec!["vec_1".to_string()];
let retrieved = storage.get(&namespace, &ids).await.unwrap();
assert_eq!(retrieved[0].values, v1_updated.values);
let ids = vec!["vec_1".to_string()];
let deleted = storage.delete(&namespace, &ids).await.unwrap();
assert_eq!(deleted, 1);
assert_eq!(storage.count(&namespace).await.unwrap(), 0);
let ids = vec!["vec_1".to_string()];
let retrieved = storage.get(&namespace, &ids).await.unwrap();
assert!(retrieved.is_empty());
}
async fn test_batch_operations<S: VectorStorage>(storage: &S, namespace: &str) {
let namespace = namespace.to_string();
storage.ensure_namespace(&namespace).await.unwrap();
let vectors: Vec<Vector> = (0..100)
.map(|i| test_vector(&format!("vec_{}", i), 8))
.collect();
let count = storage.upsert(&namespace, vectors.clone()).await.unwrap();
assert_eq!(count, 100);
assert_eq!(storage.count(&namespace).await.unwrap(), 100);
let ids: Vec<String> = (0..10).map(|i| format!("vec_{}", i)).collect();
let retrieved = storage.get(&namespace, &ids).await.unwrap();
assert_eq!(retrieved.len(), 10);
let all = storage.get_all(&namespace).await.unwrap();
assert_eq!(all.len(), 100);
let to_delete: Vec<String> = (0..50).map(|i| format!("vec_{}", i)).collect();
let deleted = storage.delete(&namespace, &to_delete).await.unwrap();
assert_eq!(deleted, 50);
assert_eq!(storage.count(&namespace).await.unwrap(), 50);
}
async fn test_namespace_isolation<S: VectorStorage>(storage: &S) {
let ns1 = "namespace_1".to_string();
let ns2 = "namespace_2".to_string();
storage.ensure_namespace(&ns1).await.unwrap();
storage.ensure_namespace(&ns2).await.unwrap();
let v1 = test_vector("same_id", 4);
let v2 = Vector {
id: "same_id".to_string(),
values: vec![100.0, 200.0, 300.0, 400.0],
metadata: None,
ttl_seconds: None,
expires_at: None,
};
storage.upsert(&ns1, vec![v1.clone()]).await.unwrap();
storage.upsert(&ns2, vec![v2.clone()]).await.unwrap();
let ids = vec!["same_id".to_string()];
let from_ns1 = storage.get(&ns1, &ids).await.unwrap();
let from_ns2 = storage.get(&ns2, &ids).await.unwrap();
assert_eq!(from_ns1[0].values, v1.values);
assert_eq!(from_ns2[0].values, v2.values);
let ids = vec!["same_id".to_string()];
storage.delete(&ns1, &ids).await.unwrap();
let ids = vec!["same_id".to_string()];
let from_ns1 = storage.get(&ns1, &ids).await.unwrap();
let from_ns2 = storage.get(&ns2, &ids).await.unwrap();
assert!(from_ns1.is_empty());
assert_eq!(from_ns2.len(), 1);
}
async fn test_dimension_mismatch<S: VectorStorage>(storage: &S, namespace: &str) {
let namespace = namespace.to_string();
storage.ensure_namespace(&namespace).await.unwrap();
let v1 = test_vector("vec_1", 4);
storage.upsert(&namespace, vec![v1]).await.unwrap();
let dim = storage.dimension(&namespace).await.unwrap();
assert_eq!(dim, Some(4));
let v2 = test_vector("vec_2", 8);
let result = storage.upsert(&namespace, vec![v2]).await;
assert!(result.is_err());
match result {
Err(DakeraError::DimensionMismatch { expected, actual }) => {
assert_eq!(expected, 4);
assert_eq!(actual, 8);
}
other => panic!("Expected DimensionMismatch error, got {:?}", other),
}
}
async fn test_metadata_handling<S: VectorStorage>(storage: &S, namespace: &str) {
let namespace = namespace.to_string();
storage.ensure_namespace(&namespace).await.unwrap();
let meta = serde_json::json!({
"category": "test",
"score": 0.95,
"tags": ["a", "b", "c"],
"nested": {
"field": "value"
}
});
let v = test_vector_with_metadata("vec_meta", 4, meta.clone());
storage.upsert(&namespace, vec![v]).await.unwrap();
let ids = vec!["vec_meta".to_string()];
let retrieved = storage.get(&namespace, &ids).await.unwrap();
assert_eq!(retrieved.len(), 1);
assert!(retrieved[0].metadata.is_some());
let retrieved_meta = retrieved[0].metadata.as_ref().unwrap();
assert_eq!(retrieved_meta["category"], "test");
assert_eq!(retrieved_meta["score"], 0.95);
assert_eq!(retrieved_meta["nested"]["field"], "value");
}
async fn test_partial_get<S: VectorStorage>(storage: &S, namespace: &str) {
let namespace = namespace.to_string();
storage.ensure_namespace(&namespace).await.unwrap();
let vectors: Vec<Vector> = (0..5)
.map(|i| test_vector(&format!("vec_{}", i), 4))
.collect();
storage.upsert(&namespace, vectors).await.unwrap();
let ids = vec![
"vec_0".to_string(),
"nonexistent_1".to_string(),
"vec_2".to_string(),
"nonexistent_2".to_string(),
];
let retrieved = storage.get(&namespace, &ids).await.unwrap();
assert_eq!(retrieved.len(), 2);
let retrieved_ids: Vec<&str> = retrieved.iter().map(|v| v.id.as_str()).collect();
assert!(retrieved_ids.contains(&"vec_0"));
assert!(retrieved_ids.contains(&"vec_2"));
}
async fn test_delete_nonexistent<S: VectorStorage>(storage: &S, namespace: &str) {
let namespace = namespace.to_string();
storage.ensure_namespace(&namespace).await.unwrap();
let ids = vec!["nonexistent".to_string()];
let deleted = storage.delete(&namespace, &ids).await.unwrap();
assert_eq!(deleted, 0);
let v = test_vector("vec_1", 4);
storage.upsert(&namespace, vec![v]).await.unwrap();
let to_delete = vec!["vec_1".to_string(), "nonexistent".to_string()];
let deleted = storage.delete(&namespace, &to_delete).await.unwrap();
assert_eq!(deleted, 1);
}
async fn test_list_namespaces<S: VectorStorage>(storage: &S) {
let ns_alpha = "ns_alpha".to_string();
let ns_beta = "ns_beta".to_string();
let ns_gamma = "ns_gamma".to_string();
storage.ensure_namespace(&ns_alpha).await.unwrap();
storage.ensure_namespace(&ns_beta).await.unwrap();
storage.ensure_namespace(&ns_gamma).await.unwrap();
let namespaces = storage.list_namespaces().await.unwrap();
assert!(namespaces.contains(&"ns_alpha".to_string()));
assert!(namespaces.contains(&"ns_beta".to_string()));
assert!(namespaces.contains(&"ns_gamma".to_string()));
}
async fn test_empty_namespace<S: VectorStorage>(storage: &S, namespace: &str) {
let namespace = namespace.to_string();
storage.ensure_namespace(&namespace).await.unwrap();
let ids = vec!["any".to_string()];
let retrieved = storage.get(&namespace, &ids).await.unwrap();
assert!(retrieved.is_empty());
let all = storage.get_all(&namespace).await.unwrap();
assert!(all.is_empty());
let ids = vec!["any".to_string()];
let deleted = storage.delete(&namespace, &ids).await.unwrap();
assert_eq!(deleted, 0);
let count = storage.count(&namespace).await.unwrap();
assert_eq!(count, 0);
let dim = storage.dimension(&namespace).await.unwrap();
assert!(dim.is_none());
}
#[tokio::test]
async fn test_memory_storage_basic_crud() {
let storage = InMemoryStorage::new();
test_basic_crud(&storage, "test_crud").await;
}
#[tokio::test]
async fn test_memory_storage_batch_operations() {
let storage = InMemoryStorage::new();
test_batch_operations(&storage, "test_batch").await;
}
#[tokio::test]
async fn test_memory_storage_namespace_isolation() {
let storage = InMemoryStorage::new();
test_namespace_isolation(&storage).await;
}
#[tokio::test]
async fn test_memory_storage_dimension_mismatch() {
let storage = InMemoryStorage::new();
test_dimension_mismatch(&storage, "test_dim").await;
}
#[tokio::test]
async fn test_memory_storage_metadata() {
let storage = InMemoryStorage::new();
test_metadata_handling(&storage, "test_meta").await;
}
#[tokio::test]
async fn test_memory_storage_partial_get() {
let storage = InMemoryStorage::new();
test_partial_get(&storage, "test_partial").await;
}
#[tokio::test]
async fn test_memory_storage_delete_nonexistent() {
let storage = InMemoryStorage::new();
test_delete_nonexistent(&storage, "test_delete").await;
}
#[tokio::test]
async fn test_memory_storage_list_namespaces() {
let storage = InMemoryStorage::new();
test_list_namespaces(&storage).await;
}
#[tokio::test]
async fn test_memory_storage_empty_namespace() {
let storage = InMemoryStorage::new();
test_empty_namespace(&storage, "test_empty").await;
}
#[tokio::test]
async fn test_object_storage_basic_crud() {
let storage = ObjectStorage::memory().unwrap();
test_basic_crud(&storage, "test_crud").await;
}
#[tokio::test]
async fn test_object_storage_batch_operations() {
let storage = ObjectStorage::memory().unwrap();
test_batch_operations(&storage, "test_batch").await;
}
#[tokio::test]
async fn test_object_storage_namespace_isolation() {
let storage = ObjectStorage::memory().unwrap();
test_namespace_isolation(&storage).await;
}
#[tokio::test]
async fn test_object_storage_dimension_mismatch() {
let storage = ObjectStorage::memory().unwrap();
test_dimension_mismatch(&storage, "test_dim").await;
}
#[tokio::test]
async fn test_object_storage_metadata() {
let storage = ObjectStorage::memory().unwrap();
test_metadata_handling(&storage, "test_meta").await;
}
#[tokio::test]
async fn test_object_storage_partial_get() {
let storage = ObjectStorage::memory().unwrap();
test_partial_get(&storage, "test_partial").await;
}
#[tokio::test]
async fn test_object_storage_delete_nonexistent() {
let storage = ObjectStorage::memory().unwrap();
test_delete_nonexistent(&storage, "test_delete").await;
}
#[tokio::test]
async fn test_object_storage_list_namespaces() {
let storage = ObjectStorage::memory().unwrap();
test_list_namespaces(&storage).await;
}
#[tokio::test]
async fn test_object_storage_empty_namespace() {
let storage = ObjectStorage::memory().unwrap();
test_empty_namespace(&storage, "test_empty").await;
}
#[tokio::test]
async fn test_memory_storage_concurrent_writes() {
use std::sync::Arc;
use tokio::task::JoinSet;
let storage = Arc::new(InMemoryStorage::new());
let namespace = "concurrent_test".to_string();
storage.ensure_namespace(&namespace).await.unwrap();
let mut tasks = JoinSet::new();
for batch_idx in 0..10 {
let storage = Arc::clone(&storage);
let ns = namespace.clone();
tasks.spawn(async move {
let vectors: Vec<Vector> = (0..10)
.map(|i| test_vector(&format!("batch_{}_vec_{}", batch_idx, i), 4))
.collect();
storage.upsert(&ns, vectors).await
});
}
while let Some(result) = tasks.join_next().await {
result.unwrap().unwrap();
}
let count = storage.count(&namespace).await.unwrap();
assert_eq!(count, 100);
}
#[tokio::test]
async fn test_memory_storage_concurrent_read_write() {
use std::sync::Arc;
use tokio::task::JoinSet;
let storage = Arc::new(InMemoryStorage::new());
let namespace = "concurrent_rw".to_string();
storage.ensure_namespace(&namespace).await.unwrap();
let initial: Vec<Vector> = (0..50)
.map(|i| test_vector(&format!("vec_{}", i), 4))
.collect();
storage.upsert(&namespace, initial).await.unwrap();
let mut tasks = JoinSet::new();
for _ in 0..5 {
let storage = Arc::clone(&storage);
let ns = namespace.clone();
tasks.spawn(async move {
for _ in 0..10 {
let ids: Vec<String> = (0..10).map(|i| format!("vec_{}", i)).collect();
let _ = storage.get(&ns, &ids).await;
}
});
}
for batch_idx in 0..5 {
let storage = Arc::clone(&storage);
let ns = namespace.clone();
tasks.spawn(async move {
let vectors: Vec<Vector> = (0..10)
.map(|i| test_vector(&format!("new_batch_{}_vec_{}", batch_idx, i), 4))
.collect();
let _ = storage.upsert(&ns, vectors).await;
});
}
while let Some(result) = tasks.join_next().await {
let _ = result.unwrap();
}
let count = storage.count(&namespace).await.unwrap();
assert_eq!(count, 100);
}
#[tokio::test]
async fn test_large_vectors() {
let storage = InMemoryStorage::new();
let namespace = "large_vec".to_string();
storage.ensure_namespace(&namespace).await.unwrap();
let large_dim = 1536;
let v = test_vector("large", large_dim);
storage.upsert(&namespace, vec![v]).await.unwrap();
let ids = vec!["large".to_string()];
let retrieved = storage.get(&namespace, &ids).await.unwrap();
assert_eq!(retrieved[0].values.len(), large_dim);
}
#[tokio::test]
async fn test_special_characters_in_ids() {
let storage = InMemoryStorage::new();
let namespace = "special_chars".to_string();
storage.ensure_namespace(&namespace).await.unwrap();
let special_ids = vec![
"id-with-dashes",
"id_with_underscores",
"id.with.dots",
"id:with:colons",
"id/with/slashes",
"id with spaces",
"id@with@at",
"unicode_日本語",
];
for id in &special_ids {
let v = test_vector(id, 4);
storage.upsert(&namespace, vec![v]).await.unwrap();
}
let ids: Vec<String> = special_ids.iter().map(|s| s.to_string()).collect();
let retrieved = storage.get(&namespace, &ids).await.unwrap();
assert_eq!(retrieved.len(), special_ids.len());
}
#[tokio::test]
async fn test_empty_metadata() {
let storage = InMemoryStorage::new();
let namespace = "empty_meta".to_string();
storage.ensure_namespace(&namespace).await.unwrap();
let v = test_vector_with_metadata("vec_1", 4, serde_json::json!({}));
storage.upsert(&namespace, vec![v]).await.unwrap();
let ids = vec!["vec_1".to_string()];
let retrieved = storage.get(&namespace, &ids).await.unwrap();
assert!(retrieved[0].metadata.is_some());
assert_eq!(
retrieved[0].metadata.as_ref().unwrap(),
&serde_json::json!({})
);
}
#[tokio::test]
async fn test_null_values_in_metadata() {
let storage = InMemoryStorage::new();
let namespace = "null_meta".to_string();
storage.ensure_namespace(&namespace).await.unwrap();
let meta = serde_json::json!({
"field": null,
"nested": {
"null_field": null,
"real_field": "value"
}
});
let v = test_vector_with_metadata("vec_1", 4, meta);
storage.upsert(&namespace, vec![v]).await.unwrap();
let ids = vec!["vec_1".to_string()];
let retrieved = storage.get(&namespace, &ids).await.unwrap();
let meta = retrieved[0].metadata.as_ref().unwrap();
assert!(meta["field"].is_null());
assert!(meta["nested"]["null_field"].is_null());
assert_eq!(meta["nested"]["real_field"], "value");
}
#[tokio::test]
async fn test_empty_batch_operations() {
let storage = InMemoryStorage::new();
let namespace = "empty_batch".to_string();
storage.ensure_namespace(&namespace).await.unwrap();
let empty_vectors: Vec<Vector> = vec![];
let count = storage.upsert(&namespace, empty_vectors).await.unwrap();
assert_eq!(count, 0);
let empty_ids: Vec<String> = vec![];
let retrieved = storage.get(&namespace, &empty_ids).await.unwrap();
assert!(retrieved.is_empty());
let deleted = storage.delete(&namespace, &empty_ids).await.unwrap();
assert_eq!(deleted, 0);
}
#[tokio::test]
async fn test_duplicate_ids_in_batch() {
let storage = InMemoryStorage::new();
let namespace = "dup_ids".to_string();
storage.ensure_namespace(&namespace).await.unwrap();
let vectors = vec![
Vector {
id: "dup".to_string(),
values: vec![1.0, 1.0, 1.0, 1.0],
metadata: None,
ttl_seconds: None,
expires_at: None,
},
Vector {
id: "dup".to_string(),
values: vec![2.0, 2.0, 2.0, 2.0],
metadata: None,
ttl_seconds: None,
expires_at: None,
},
];
storage.upsert(&namespace, vectors).await.unwrap();
let count = storage.count(&namespace).await.unwrap();
assert_eq!(count, 1);
let ids = vec!["dup".to_string()];
let retrieved = storage.get(&namespace, &ids).await.unwrap();
assert_eq!(retrieved[0].values, vec![2.0, 2.0, 2.0, 2.0]);
}