use std::collections::HashMap;
use std::ops::Range;
use std::sync::Arc;
use g_math::fixed_point::FixedPoint;
use log::trace;
use super::tree_tensor::{HyperbolicTreeTensor, HTTConfig, SharedHTT, IntegrationError, IntegrationResult};
use super::config::HTTStorageConfig;
pub struct HTTStorage {
htt: SharedHTT,
config: HTTStorageConfig,
}
impl HTTStorage {
pub fn new(config: HTTStorageConfig) -> Self {
let mut htt_config = HTTConfig::new(
config.dimension,
config.max_memory_nodes,
config.cache_size,
);
if config.grid_resolution > 0 {
htt_config = htt_config.with_grid_resolution(config.grid_resolution);
}
if config.tau > FixedPoint::from_int(0) {
htt_config = htt_config.with_tau(config.tau);
}
let htt = Arc::new(HyperbolicTreeTensor::new(htt_config));
htt.insert("/", vec![], Some("application/x-directory".to_string()))
.expect("failed to initialize HTT root node ('/')");
Self { htt, config }
}
pub fn shared_htt(&self) -> &SharedHTT {
&self.htt
}
pub fn store_data_only(&self, key: &str, value: &[u8], content_type: Option<String>) -> IntegrationResult<()> {
let normalized_key = Self::normalize_key(key);
let missing_ancestors = Self::find_missing_ancestors(&self.htt, &normalized_key);
for ancestor in &missing_ancestors {
if !self.htt.exists(ancestor) {
match self.htt.insert_data_only(ancestor, vec![], Some("application/x-directory".to_string())) {
Ok(()) => {},
Err(IntegrationError::AlreadyExists(_)) => {},
Err(e) => return Err(e),
}
}
}
if self.htt.exists(&normalized_key) {
self.htt.update_value(&normalized_key, value.to_vec())?;
} else {
match self.htt.insert_data_only(&normalized_key, value.to_vec(), content_type) {
Ok(()) => {},
Err(IntegrationError::AlreadyExists(_)) => {
self.htt.update_value(&normalized_key, value.to_vec())?;
},
Err(e) => return Err(e),
}
}
Ok(())
}
pub fn store(&self, key: &str, value: &[u8], content_type: Option<String>) -> IntegrationResult<()> {
trace!("HTTStorage::store - key: {}, size: {} bytes", key, value.len());
let normalized_key = Self::normalize_key(key);
let missing_ancestors = Self::find_missing_ancestors(&self.htt, &normalized_key);
for ancestor in &missing_ancestors {
if !self.htt.exists(ancestor) {
match self.htt.insert(
ancestor,
vec![],
Some("application/x-directory".to_string()),
) {
Ok(()) => {},
Err(IntegrationError::AlreadyExists(_)) => {},
Err(e) => return Err(e),
}
}
}
if self.htt.exists(&normalized_key) {
self.htt.update_value(&normalized_key, value.to_vec())?;
} else {
match self.htt.insert(&normalized_key, value.to_vec(), content_type) {
Ok(()) => {},
Err(IntegrationError::AlreadyExists(_)) => {
self.htt.update_value(&normalized_key, value.to_vec())?;
},
Err(e) => return Err(e),
}
}
Ok(())
}
pub fn store_positioned(&self, key: &str, value: &[u8], content_type: Option<String>, child_index: u32) -> IntegrationResult<()> {
let normalized_key = Self::normalize_key(key);
let missing_ancestors = Self::find_missing_ancestors(&self.htt, &normalized_key);
for ancestor in &missing_ancestors {
if !self.htt.exists(ancestor) {
match self.htt.insert(
ancestor,
vec![],
Some("application/x-directory".to_string()),
) {
Ok(()) => {},
Err(IntegrationError::AlreadyExists(_)) => {},
Err(e) => return Err(e),
}
}
}
if self.htt.exists(&normalized_key) {
self.htt.update_value(&normalized_key, value.to_vec())?;
} else {
match self.htt.insert_positioned(&normalized_key, value.to_vec(), content_type, child_index) {
Ok(()) => {},
Err(IntegrationError::AlreadyExists(_)) => {
self.htt.update_value(&normalized_key, value.to_vec())?;
},
Err(e) => return Err(e),
}
}
Ok(())
}
pub fn retrieve(&self, key: &str) -> IntegrationResult<Vec<u8>> {
trace!("HTTStorage::retrieve - key: {}", key);
let normalized_key = Self::normalize_key(key);
let node = self.htt.get(&normalized_key)?;
Ok(node.value().to_vec())
}
pub fn delete(&self, key: &str) -> IntegrationResult<()> {
trace!("HTTStorage::delete - key: {}", key);
let normalized_key = Self::normalize_key(key);
if normalized_key == "/" {
return Err(IntegrationError::ValidationFailed(
"Cannot delete root node".to_string(),
));
}
self.htt.delete(&normalized_key)
}
pub fn list(&self, prefix: &str) -> IntegrationResult<Vec<String>> {
trace!("HTTStorage::list - prefix: {}", prefix);
let normalized_prefix = Self::normalize_key(prefix);
self.htt.list_subtree(&normalized_prefix)
}
pub fn exists(&self, key: &str) -> bool {
let normalized_key = Self::normalize_key(key);
self.htt.exists(&normalized_key)
}
pub fn get_metadata(&self, key: &str) -> IntegrationResult<HashMap<String, String>> {
trace!("HTTStorage::get_metadata - key: {}", key);
let normalized_key = Self::normalize_key(key);
let node = self.htt.get(&normalized_key)?;
let meta = node.metadata();
let mut result = meta.metadata.clone();
result.insert("key".to_string(), meta.key.clone());
result.insert("size".to_string(), node.value().len().to_string());
result.insert("created_at".to_string(), meta.created_at.to_string());
result.insert("updated_at".to_string(), meta.updated_at.to_string());
if let Some(ref ct) = meta.content_type {
result.insert("content_type".to_string(), ct.clone());
}
Ok(result)
}
pub fn set_metadata(&self, key: &str, meta_key: &str, meta_value: &str) -> IntegrationResult<()> {
trace!("HTTStorage::set_metadata - key: {}, meta_key: {}", key, meta_key);
let normalized_key = Self::normalize_key(key);
self.htt.set_node_metadata(&normalized_key, meta_key, meta_value)
}
pub fn set_semantic(&self, key: &str, coords: Vec<u8>) -> IntegrationResult<()> {
trace!("HTTStorage::set_semantic - key: {}, bytes: {}", key, coords.len());
let normalized_key = Self::normalize_key(key);
self.htt.set_semantic(&normalized_key, coords)
}
pub fn get_semantic(&self, key: &str) -> IntegrationResult<Vec<u8>> {
trace!("HTTStorage::get_semantic - key: {}", key);
let normalized_key = Self::normalize_key(key);
self.htt.get_semantic(&normalized_key)
}
pub fn position(&self, key: &str) -> IntegrationResult<crate::hyperbolic_geometry::HyperbolicPoint> {
let normalized_key = Self::normalize_key(key);
self.htt.position(&normalized_key)
}
pub fn embed_existing(&self, key: &str) -> IntegrationResult<bool> {
let normalized_key = Self::normalize_key(key);
self.htt.embed_existing(&normalized_key)
}
pub fn semantic_epoch(&self) -> u64 {
self.htt.tensor_network().semantic_epoch()
}
pub fn find_nearest(&self, path: &str, k: usize) -> IntegrationResult<Vec<String>> {
let normalized = Self::normalize_key(path);
let results = self.htt.find_nearest(&normalized, k)?;
Ok(results.into_iter().map(|(p, _dist)| p).collect())
}
pub fn find_in_radius(&self, path: &str, radius: FixedPoint) -> IntegrationResult<Vec<String>> {
let normalized = Self::normalize_key(path);
let results = self.htt.find_in_radius(&normalized, radius)?;
Ok(results.into_iter().map(|(p, _dist)| p).collect())
}
pub fn nearest_semantic(
&self,
query_coords: &[u8],
k: usize,
dim_range: &Range<usize>,
) -> IntegrationResult<Vec<(String, FixedPoint)>> {
self.htt.nearest_semantic(query_coords, k, dim_range)
}
pub fn neighbors_semantic(
&self,
path: &str,
k: usize,
dim_range: &Range<usize>,
) -> IntegrationResult<Vec<(String, FixedPoint)>> {
let normalized = Self::normalize_key(path);
self.htt.neighbors_semantic(&normalized, k, dim_range)
}
pub fn nearest_neighbor_point(&self, coords: &[FixedPoint]) -> IntegrationResult<(String, FixedPoint)> {
self.validate_query_coords(coords)?;
let query = super::hyperbolic_geometry::HyperbolicPoint::from_slice(coords);
self.htt.nearest_neighbor_point(&query)
}
pub fn nearest_neighbor_point_k(&self, coords: &[FixedPoint], k: usize) -> IntegrationResult<Vec<(String, FixedPoint)>> {
self.validate_query_coords(coords)?;
let query = super::hyperbolic_geometry::HyperbolicPoint::from_slice(coords);
self.htt.nearest_neighbor_point_k(&query, k)
}
fn validate_query_coords(&self, coords: &[FixedPoint]) -> IntegrationResult<()> {
if coords.len() != self.config.dimension {
return Err(IntegrationError::ValidationFailed(format!(
"query has {} coordinates but the store dimension is {}",
coords.len(),
self.config.dimension
)));
}
Ok(())
}
fn find_missing_ancestors(htt: &HyperbolicTreeTensor, path: &str) -> Vec<String> {
let mut missing = Vec::new();
let mut current = path.to_string();
loop {
let parent = match current.rfind('/') {
Some(index) if index > 0 => current[0..index].to_string(),
Some(0) if current != "/" => "/".to_string(),
_ => break,
};
if parent == current {
break;
}
if htt.exists(&parent) {
break;
}
missing.push(parent.clone());
current = parent;
}
missing.reverse(); missing
}
pub fn node_count(&self) -> usize {
self.htt.node_count()
}
pub fn stats(&self) -> HashMap<String, String> {
let mut stats = HashMap::new();
for (key, value) in self.htt.stats() {
stats.insert(format!("htt.{}", key), value);
}
stats.insert("node_count".to_string(), self.htt.node_count().to_string());
stats.insert("dimension".to_string(), self.config.dimension.to_string());
stats.insert(
"max_memory_nodes".to_string(),
self.config.max_memory_nodes.to_string(),
);
stats.insert("cache_size".to_string(), self.config.cache_size.to_string());
stats
}
fn normalize_key(key: &str) -> String {
if !key.starts_with('/') {
format!("/{}", key)
} else {
key.to_string()
}
}
}
#[cfg(test)]
mod tests {
fn fp(vals: &[f64]) -> Vec<g_math::fixed_point::FixedPoint> {
vals.iter().map(|&v| g_math::fixed_point::FixedPoint::from_f64(v)).collect()
}
use super::*;
#[test]
fn test_storage_creation() {
let config = HTTStorageConfig::default();
let storage = HTTStorage::new(config);
assert!(storage.exists("/"));
}
#[test]
fn test_storage_operations() {
let config = HTTStorageConfig::default();
let storage = HTTStorage::new(config);
storage.store("/test", b"test data", None).unwrap();
assert!(storage.exists("/test"));
let data = storage.retrieve("/test").unwrap();
assert_eq!(data, b"test data");
storage.store("/test", b"updated data", None).unwrap();
let updated = storage.retrieve("/test").unwrap();
assert_eq!(updated, b"updated data");
storage.store("/parent/child", b"child data", None).unwrap();
assert!(storage.exists("/parent"));
let keys = storage.list("/").unwrap();
assert!(keys.contains(&"/test".to_string()));
assert!(keys.contains(&"/parent".to_string()));
assert!(keys.contains(&"/parent/child".to_string()));
storage.delete("/test").unwrap();
assert!(!storage.exists("/test"));
storage
.set_metadata("/parent", "description", "A parent directory")
.unwrap();
let metadata = storage.get_metadata("/parent").unwrap();
assert_eq!(
metadata.get("description"),
Some(&"A parent directory".to_string())
);
}
#[test]
fn test_find_nearest_api() {
let config = HTTStorageConfig::default();
let storage = HTTStorage::new(config);
storage.store("/a", b"a", None).unwrap();
storage.store("/b", b"b", None).unwrap();
storage.store("/c", b"c", None).unwrap();
let nearest = storage.find_nearest("/a", 2).unwrap();
assert!(!nearest.is_empty());
assert!(nearest.len() <= 2);
assert!(!nearest.contains(&"/a".to_string()));
}
#[test]
fn test_find_in_radius_api() {
use g_math::fixed_point::FixedPoint;
let config = HTTStorageConfig::default();
let storage = HTTStorage::new(config);
storage.store("/x", b"x", None).unwrap();
storage.store("/y", b"y", None).unwrap();
let large_radius = FixedPoint::from_int(10);
let results = storage.find_in_radius("/x", large_radius).unwrap();
assert!(!results.is_empty());
}
#[test]
fn test_storage_stats() {
let config = HTTStorageConfig::default();
let storage = HTTStorage::new(config);
storage.store("/test1", b"data1", None).unwrap();
storage.store("/test2", b"data2", None).unwrap();
let stats = storage.stats();
assert!(stats.contains_key("node_count"));
assert!(stats.contains_key("dimension"));
}
#[test]
fn test_nearest_neighbor_point_api() {
let config = HTTStorageConfig::default();
let storage = HTTStorage::new(config);
storage.store("/a", b"a", None).unwrap();
storage.store("/b", b"b", None).unwrap();
storage.store("/c", b"c", None).unwrap();
let (path, dist) = storage.nearest_neighbor_point(&fp(&[0.0, 0.0, 0.0, 0.0])).unwrap();
assert_eq!(path, "/", "Query at origin should find root");
let tolerance = FixedPoint::from_int(1) / FixedPoint::from_int(10);
assert!(dist < tolerance, "Distance to root at origin should be small");
}
}