1use std::collections::HashMap;
17use std::ops::Range;
18use std::sync::Arc;
19use g_math::fixed_point::FixedPoint;
20use log::trace;
21use super::tree_tensor::{HyperbolicTreeTensor, HTTConfig, SharedHTT, IntegrationError, IntegrationResult};
22use super::config::HTTStorageConfig;
23
24pub struct HTTStorage {
29 htt: SharedHTT,
31 config: HTTStorageConfig,
33}
34
35impl HTTStorage {
36 pub fn new(config: HTTStorageConfig) -> Self {
38 let mut htt_config = HTTConfig::new(
39 config.dimension,
40 config.max_memory_nodes,
41 config.cache_size,
42 );
43 if config.grid_resolution > 0 {
44 htt_config = htt_config.with_grid_resolution(config.grid_resolution);
45 }
46 if config.tau > FixedPoint::from_int(0) {
47 htt_config = htt_config.with_tau(config.tau);
48 }
49
50 let htt = Arc::new(HyperbolicTreeTensor::new(htt_config));
51
52 htt.insert("/", vec![], Some("application/x-directory".to_string()))
58 .expect("failed to initialize HTT root node ('/')");
59
60 Self { htt, config }
61 }
62
63 pub fn shared_htt(&self) -> &SharedHTT {
65 &self.htt
66 }
67
68 pub fn store_data_only(&self, key: &str, value: &[u8], content_type: Option<String>) -> IntegrationResult<()> {
73 let normalized_key = Self::normalize_key(key);
74
75 let missing_ancestors = Self::find_missing_ancestors(&self.htt, &normalized_key);
76 for ancestor in &missing_ancestors {
77 if !self.htt.exists(ancestor) {
78 match self.htt.insert_data_only(ancestor, vec![], Some("application/x-directory".to_string())) {
79 Ok(()) => {},
80 Err(IntegrationError::AlreadyExists(_)) => {},
81 Err(e) => return Err(e),
82 }
83 }
84 }
85
86 if self.htt.exists(&normalized_key) {
87 self.htt.update_value(&normalized_key, value.to_vec())?;
88 } else {
89 match self.htt.insert_data_only(&normalized_key, value.to_vec(), content_type) {
90 Ok(()) => {},
91 Err(IntegrationError::AlreadyExists(_)) => {
92 self.htt.update_value(&normalized_key, value.to_vec())?;
93 },
94 Err(e) => return Err(e),
95 }
96 }
97
98 Ok(())
99 }
100
101 pub fn store(&self, key: &str, value: &[u8], content_type: Option<String>) -> IntegrationResult<()> {
107 trace!("HTTStorage::store - key: {}, size: {} bytes", key, value.len());
108
109 let normalized_key = Self::normalize_key(key);
110
111 let missing_ancestors = Self::find_missing_ancestors(&self.htt, &normalized_key);
113
114 for ancestor in &missing_ancestors {
118 if !self.htt.exists(ancestor) {
119 match self.htt.insert(
120 ancestor,
121 vec![],
122 Some("application/x-directory".to_string()),
123 ) {
124 Ok(()) => {},
125 Err(IntegrationError::AlreadyExists(_)) => {},
126 Err(e) => return Err(e),
127 }
128 }
129 }
130
131 if self.htt.exists(&normalized_key) {
135 self.htt.update_value(&normalized_key, value.to_vec())?;
136 } else {
137 match self.htt.insert(&normalized_key, value.to_vec(), content_type) {
138 Ok(()) => {},
139 Err(IntegrationError::AlreadyExists(_)) => {
140 self.htt.update_value(&normalized_key, value.to_vec())?;
141 },
142 Err(e) => return Err(e),
143 }
144 }
145
146 Ok(())
147 }
148
149 pub fn store_positioned(&self, key: &str, value: &[u8], content_type: Option<String>, child_index: u32) -> IntegrationResult<()> {
154 let normalized_key = Self::normalize_key(key);
155
156 let missing_ancestors = Self::find_missing_ancestors(&self.htt, &normalized_key);
157 for ancestor in &missing_ancestors {
158 if !self.htt.exists(ancestor) {
159 match self.htt.insert(
160 ancestor,
161 vec![],
162 Some("application/x-directory".to_string()),
163 ) {
164 Ok(()) => {},
165 Err(IntegrationError::AlreadyExists(_)) => {},
166 Err(e) => return Err(e),
167 }
168 }
169 }
170
171 if self.htt.exists(&normalized_key) {
172 self.htt.update_value(&normalized_key, value.to_vec())?;
173 } else {
174 match self.htt.insert_positioned(&normalized_key, value.to_vec(), content_type, child_index) {
175 Ok(()) => {},
176 Err(IntegrationError::AlreadyExists(_)) => {
177 self.htt.update_value(&normalized_key, value.to_vec())?;
178 },
179 Err(e) => return Err(e),
180 }
181 }
182
183 Ok(())
184 }
185
186 pub fn retrieve(&self, key: &str) -> IntegrationResult<Vec<u8>> {
188 trace!("HTTStorage::retrieve - key: {}", key);
189
190 let normalized_key = Self::normalize_key(key);
191 let node = self.htt.get(&normalized_key)?;
192 Ok(node.value().to_vec())
193 }
194
195 pub fn delete(&self, key: &str) -> IntegrationResult<()> {
197 trace!("HTTStorage::delete - key: {}", key);
198
199 let normalized_key = Self::normalize_key(key);
200
201 if normalized_key == "/" {
202 return Err(IntegrationError::ValidationFailed(
203 "Cannot delete root node".to_string(),
204 ));
205 }
206
207 self.htt.delete(&normalized_key)
208 }
209
210 pub fn list(&self, prefix: &str) -> IntegrationResult<Vec<String>> {
212 trace!("HTTStorage::list - prefix: {}", prefix);
213
214 let normalized_prefix = Self::normalize_key(prefix);
215 self.htt.list_subtree(&normalized_prefix)
216 }
217
218 pub fn exists(&self, key: &str) -> bool {
220 let normalized_key = Self::normalize_key(key);
221 self.htt.exists(&normalized_key)
222 }
223
224 pub fn get_metadata(&self, key: &str) -> IntegrationResult<HashMap<String, String>> {
226 trace!("HTTStorage::get_metadata - key: {}", key);
227
228 let normalized_key = Self::normalize_key(key);
229 let node = self.htt.get(&normalized_key)?;
230 let meta = node.metadata();
231
232 let mut result = meta.metadata.clone();
233 result.insert("key".to_string(), meta.key.clone());
234 result.insert("size".to_string(), node.value().len().to_string());
235 result.insert("created_at".to_string(), meta.created_at.to_string());
236 result.insert("updated_at".to_string(), meta.updated_at.to_string());
237
238 if let Some(ref ct) = meta.content_type {
239 result.insert("content_type".to_string(), ct.clone());
240 }
241
242 Ok(result)
243 }
244
245 pub fn set_metadata(&self, key: &str, meta_key: &str, meta_value: &str) -> IntegrationResult<()> {
247 trace!("HTTStorage::set_metadata - key: {}, meta_key: {}", key, meta_key);
248
249 let normalized_key = Self::normalize_key(key);
250 self.htt.set_node_metadata(&normalized_key, meta_key, meta_value)
251 }
252
253 pub fn set_semantic(&self, key: &str, coords: Vec<u8>) -> IntegrationResult<()> {
255 trace!("HTTStorage::set_semantic - key: {}, bytes: {}", key, coords.len());
256
257 let normalized_key = Self::normalize_key(key);
258 self.htt.set_semantic(&normalized_key, coords)
259 }
260
261 pub fn get_semantic(&self, key: &str) -> IntegrationResult<Vec<u8>> {
263 trace!("HTTStorage::get_semantic - key: {}", key);
264
265 let normalized_key = Self::normalize_key(key);
266 self.htt.get_semantic(&normalized_key)
267 }
268
269 pub fn position(&self, key: &str) -> IntegrationResult<crate::hyperbolic_geometry::HyperbolicPoint> {
271 let normalized_key = Self::normalize_key(key);
272 self.htt.position(&normalized_key)
273 }
274
275 pub fn embed_existing(&self, key: &str) -> IntegrationResult<bool> {
279 let normalized_key = Self::normalize_key(key);
280 self.htt.embed_existing(&normalized_key)
281 }
282
283 pub fn semantic_epoch(&self) -> u64 {
286 self.htt.tensor_network().semantic_epoch()
287 }
288
289 pub fn find_nearest(&self, path: &str, k: usize) -> IntegrationResult<Vec<String>> {
292 let normalized = Self::normalize_key(path);
293 let results = self.htt.find_nearest(&normalized, k)?;
294 Ok(results.into_iter().map(|(p, _dist)| p).collect())
295 }
296
297 pub fn find_in_radius(&self, path: &str, radius: FixedPoint) -> IntegrationResult<Vec<String>> {
299 let normalized = Self::normalize_key(path);
300 let results = self.htt.find_in_radius(&normalized, radius)?;
301 Ok(results.into_iter().map(|(p, _dist)| p).collect())
302 }
303
304 pub fn nearest_semantic(
316 &self,
317 query_coords: &[u8],
318 k: usize,
319 dim_range: &Range<usize>,
320 ) -> IntegrationResult<Vec<(String, FixedPoint)>> {
321 self.htt.nearest_semantic(query_coords, k, dim_range)
322 }
323
324 pub fn neighbors_semantic(
327 &self,
328 path: &str,
329 k: usize,
330 dim_range: &Range<usize>,
331 ) -> IntegrationResult<Vec<(String, FixedPoint)>> {
332 let normalized = Self::normalize_key(path);
333 self.htt.neighbors_semantic(&normalized, k, dim_range)
334 }
335
336 pub fn nearest_neighbor_point(&self, coords: &[FixedPoint]) -> IntegrationResult<(String, FixedPoint)> {
349 self.validate_query_coords(coords)?;
350 let query = super::hyperbolic_geometry::HyperbolicPoint::from_slice(coords);
351 self.htt.nearest_neighbor_point(&query)
352 }
353
354 pub fn nearest_neighbor_point_k(&self, coords: &[FixedPoint], k: usize) -> IntegrationResult<Vec<(String, FixedPoint)>> {
358 self.validate_query_coords(coords)?;
359 let query = super::hyperbolic_geometry::HyperbolicPoint::from_slice(coords);
360 self.htt.nearest_neighbor_point_k(&query, k)
361 }
362
363 fn validate_query_coords(&self, coords: &[FixedPoint]) -> IntegrationResult<()> {
367 if coords.len() != self.config.dimension {
368 return Err(IntegrationError::ValidationFailed(format!(
369 "query has {} coordinates but the store dimension is {}",
370 coords.len(),
371 self.config.dimension
372 )));
373 }
374 Ok(())
375 }
376
377 fn find_missing_ancestors(htt: &HyperbolicTreeTensor, path: &str) -> Vec<String> {
380 let mut missing = Vec::new();
381 let mut current = path.to_string();
382
383 loop {
384 let parent = match current.rfind('/') {
385 Some(index) if index > 0 => current[0..index].to_string(),
386 Some(0) if current != "/" => "/".to_string(),
387 _ => break,
388 };
389
390 if parent == current {
391 break;
392 }
393
394 if htt.exists(&parent) {
395 break;
396 }
397
398 missing.push(parent.clone());
399 current = parent;
400 }
401
402 missing.reverse(); missing
404 }
405
406 pub fn node_count(&self) -> usize {
408 self.htt.node_count()
409 }
410
411 pub fn stats(&self) -> HashMap<String, String> {
413 let mut stats = HashMap::new();
414
415 for (key, value) in self.htt.stats() {
416 stats.insert(format!("htt.{}", key), value);
417 }
418 stats.insert("node_count".to_string(), self.htt.node_count().to_string());
419
420 stats.insert("dimension".to_string(), self.config.dimension.to_string());
421 stats.insert(
422 "max_memory_nodes".to_string(),
423 self.config.max_memory_nodes.to_string(),
424 );
425 stats.insert("cache_size".to_string(), self.config.cache_size.to_string());
426
427 stats
428 }
429
430 fn normalize_key(key: &str) -> String {
432 if !key.starts_with('/') {
433 format!("/{}", key)
434 } else {
435 key.to_string()
436 }
437 }
438}
439
440#[cfg(test)]
441mod tests {
442
443fn fp(vals: &[f64]) -> Vec<g_math::fixed_point::FixedPoint> {
445 vals.iter().map(|&v| g_math::fixed_point::FixedPoint::from_f64(v)).collect()
446}
447
448 use super::*;
449
450 #[test]
451 fn test_storage_creation() {
452 let config = HTTStorageConfig::default();
453 let storage = HTTStorage::new(config);
454 assert!(storage.exists("/"));
455 }
456
457 #[test]
458 fn test_storage_operations() {
459 let config = HTTStorageConfig::default();
460 let storage = HTTStorage::new(config);
461
462 storage.store("/test", b"test data", None).unwrap();
464 assert!(storage.exists("/test"));
465
466 let data = storage.retrieve("/test").unwrap();
468 assert_eq!(data, b"test data");
469
470 storage.store("/test", b"updated data", None).unwrap();
472 let updated = storage.retrieve("/test").unwrap();
473 assert_eq!(updated, b"updated data");
474
475 storage.store("/parent/child", b"child data", None).unwrap();
477 assert!(storage.exists("/parent"));
478
479 let keys = storage.list("/").unwrap();
481 assert!(keys.contains(&"/test".to_string()));
482 assert!(keys.contains(&"/parent".to_string()));
483 assert!(keys.contains(&"/parent/child".to_string()));
484
485 storage.delete("/test").unwrap();
487 assert!(!storage.exists("/test"));
488
489 storage
491 .set_metadata("/parent", "description", "A parent directory")
492 .unwrap();
493 let metadata = storage.get_metadata("/parent").unwrap();
494 assert_eq!(
495 metadata.get("description"),
496 Some(&"A parent directory".to_string())
497 );
498 }
499
500 #[test]
501 fn test_find_nearest_api() {
502 let config = HTTStorageConfig::default();
503 let storage = HTTStorage::new(config);
504
505 storage.store("/a", b"a", None).unwrap();
506 storage.store("/b", b"b", None).unwrap();
507 storage.store("/c", b"c", None).unwrap();
508
509 let nearest = storage.find_nearest("/a", 2).unwrap();
510 assert!(!nearest.is_empty());
511 assert!(nearest.len() <= 2);
512 assert!(!nearest.contains(&"/a".to_string()));
514 }
515
516 #[test]
517 fn test_find_in_radius_api() {
518 use g_math::fixed_point::FixedPoint;
519
520 let config = HTTStorageConfig::default();
521 let storage = HTTStorage::new(config);
522
523 storage.store("/x", b"x", None).unwrap();
524 storage.store("/y", b"y", None).unwrap();
525
526 let large_radius = FixedPoint::from_int(10);
527 let results = storage.find_in_radius("/x", large_radius).unwrap();
528 assert!(!results.is_empty());
530 }
531
532 #[test]
533 fn test_storage_stats() {
534 let config = HTTStorageConfig::default();
535 let storage = HTTStorage::new(config);
536
537 storage.store("/test1", b"data1", None).unwrap();
538 storage.store("/test2", b"data2", None).unwrap();
539
540 let stats = storage.stats();
541 assert!(stats.contains_key("node_count"));
542 assert!(stats.contains_key("dimension"));
543 }
544
545 #[test]
546 fn test_nearest_neighbor_point_api() {
547 let config = HTTStorageConfig::default();
548 let storage = HTTStorage::new(config);
549
550 storage.store("/a", b"a", None).unwrap();
551 storage.store("/b", b"b", None).unwrap();
552 storage.store("/c", b"c", None).unwrap();
553
554 let (path, dist) = storage.nearest_neighbor_point(&fp(&[0.0, 0.0, 0.0, 0.0])).unwrap();
556 assert_eq!(path, "/", "Query at origin should find root");
557 let tolerance = FixedPoint::from_int(1) / FixedPoint::from_int(10);
558 assert!(dist < tolerance, "Distance to root at origin should be small");
559 }
560}