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)> {
342 self.validate_query_coords(coords)?;
343 let query = super::hyperbolic_geometry::HyperbolicPoint::from_slice(coords);
344 self.htt.nearest_neighbor_point(&query)
345 }
346
347 pub fn nearest_neighbor_point_k(&self, coords: &[FixedPoint], k: usize) -> IntegrationResult<Vec<(String, FixedPoint)>> {
351 self.validate_query_coords(coords)?;
352 let query = super::hyperbolic_geometry::HyperbolicPoint::from_slice(coords);
353 self.htt.nearest_neighbor_point_k(&query, k)
354 }
355
356 fn validate_query_coords(&self, coords: &[FixedPoint]) -> IntegrationResult<()> {
360 if coords.len() != self.config.dimension {
361 return Err(IntegrationError::ValidationFailed(format!(
362 "query has {} coordinates but the store dimension is {}",
363 coords.len(),
364 self.config.dimension
365 )));
366 }
367 Ok(())
368 }
369
370 fn find_missing_ancestors(htt: &HyperbolicTreeTensor, path: &str) -> Vec<String> {
373 let mut missing = Vec::new();
374 let mut current = path.to_string();
375
376 loop {
377 let parent = match current.rfind('/') {
378 Some(index) if index > 0 => current[0..index].to_string(),
379 Some(0) if current != "/" => "/".to_string(),
380 _ => break,
381 };
382
383 if parent == current {
384 break;
385 }
386
387 if htt.exists(&parent) {
388 break;
389 }
390
391 missing.push(parent.clone());
392 current = parent;
393 }
394
395 missing.reverse(); missing
397 }
398
399 pub fn node_count(&self) -> usize {
401 self.htt.node_count()
402 }
403
404 pub fn stats(&self) -> HashMap<String, String> {
406 let mut stats = HashMap::new();
407
408 for (key, value) in self.htt.stats() {
409 stats.insert(format!("htt.{}", key), value);
410 }
411 stats.insert("node_count".to_string(), self.htt.node_count().to_string());
412
413 stats.insert("dimension".to_string(), self.config.dimension.to_string());
414 stats.insert(
415 "max_memory_nodes".to_string(),
416 self.config.max_memory_nodes.to_string(),
417 );
418 stats.insert("cache_size".to_string(), self.config.cache_size.to_string());
419
420 stats
421 }
422
423 fn normalize_key(key: &str) -> String {
425 if !key.starts_with('/') {
426 format!("/{}", key)
427 } else {
428 key.to_string()
429 }
430 }
431}
432
433#[cfg(test)]
434mod tests {
435
436fn fp(vals: &[f64]) -> Vec<g_math::fixed_point::FixedPoint> {
438 vals.iter().map(|&v| g_math::fixed_point::FixedPoint::from_f64(v)).collect()
439}
440
441 use super::*;
442
443 #[test]
444 fn test_storage_creation() {
445 let config = HTTStorageConfig::default();
446 let storage = HTTStorage::new(config);
447 assert!(storage.exists("/"));
448 }
449
450 #[test]
451 fn test_storage_operations() {
452 let config = HTTStorageConfig::default();
453 let storage = HTTStorage::new(config);
454
455 storage.store("/test", b"test data", None).unwrap();
457 assert!(storage.exists("/test"));
458
459 let data = storage.retrieve("/test").unwrap();
461 assert_eq!(data, b"test data");
462
463 storage.store("/test", b"updated data", None).unwrap();
465 let updated = storage.retrieve("/test").unwrap();
466 assert_eq!(updated, b"updated data");
467
468 storage.store("/parent/child", b"child data", None).unwrap();
470 assert!(storage.exists("/parent"));
471
472 let keys = storage.list("/").unwrap();
474 assert!(keys.contains(&"/test".to_string()));
475 assert!(keys.contains(&"/parent".to_string()));
476 assert!(keys.contains(&"/parent/child".to_string()));
477
478 storage.delete("/test").unwrap();
480 assert!(!storage.exists("/test"));
481
482 storage
484 .set_metadata("/parent", "description", "A parent directory")
485 .unwrap();
486 let metadata = storage.get_metadata("/parent").unwrap();
487 assert_eq!(
488 metadata.get("description"),
489 Some(&"A parent directory".to_string())
490 );
491 }
492
493 #[test]
494 fn test_find_nearest_api() {
495 let config = HTTStorageConfig::default();
496 let storage = HTTStorage::new(config);
497
498 storage.store("/a", b"a", None).unwrap();
499 storage.store("/b", b"b", None).unwrap();
500 storage.store("/c", b"c", None).unwrap();
501
502 let nearest = storage.find_nearest("/a", 2).unwrap();
503 assert!(!nearest.is_empty());
504 assert!(nearest.len() <= 2);
505 assert!(!nearest.contains(&"/a".to_string()));
507 }
508
509 #[test]
510 fn test_find_in_radius_api() {
511 use g_math::fixed_point::FixedPoint;
512
513 let config = HTTStorageConfig::default();
514 let storage = HTTStorage::new(config);
515
516 storage.store("/x", b"x", None).unwrap();
517 storage.store("/y", b"y", None).unwrap();
518
519 let large_radius = FixedPoint::from_int(10);
520 let results = storage.find_in_radius("/x", large_radius).unwrap();
521 assert!(!results.is_empty());
523 }
524
525 #[test]
526 fn test_storage_stats() {
527 let config = HTTStorageConfig::default();
528 let storage = HTTStorage::new(config);
529
530 storage.store("/test1", b"data1", None).unwrap();
531 storage.store("/test2", b"data2", None).unwrap();
532
533 let stats = storage.stats();
534 assert!(stats.contains_key("node_count"));
535 assert!(stats.contains_key("dimension"));
536 }
537
538 #[test]
539 fn test_nearest_neighbor_point_api() {
540 let config = HTTStorageConfig::default();
541 let storage = HTTStorage::new(config);
542
543 storage.store("/a", b"a", None).unwrap();
544 storage.store("/b", b"b", None).unwrap();
545 storage.store("/c", b"c", None).unwrap();
546
547 let (path, dist) = storage.nearest_neighbor_point(&fp(&[0.0, 0.0, 0.0, 0.0])).unwrap();
549 assert_eq!(path, "/", "Query at origin should find root");
550 let tolerance = FixedPoint::from_int(1) / FixedPoint::from_int(10);
551 assert!(dist < tolerance, "Distance to root at origin should be small");
552 }
553}