1use crate::{similarity::SimilarityMetric, Vector, VectorId, VectorStoreTrait};
7use anyhow::{anyhow, Result};
8use oxirs_core::model::{GraphName, Literal, NamedNode, Term};
9use parking_lot::RwLock;
15use serde::{Deserialize, Serialize};
16use std::collections::{HashMap, HashSet};
17use std::hash::{Hash, Hasher};
18use std::sync::Arc;
19
20#[derive(Debug, Clone, Serialize, Deserialize)]
22pub struct RdfVectorConfig {
23 pub uri_decomposition: bool,
25 pub include_literal_types: bool,
27 pub graph_context: bool,
29 pub namespace_aware: bool,
31 pub default_metric: SimilarityMetric,
33 pub cache_size: usize,
35}
36
37impl Default for RdfVectorConfig {
38 fn default() -> Self {
39 Self {
40 uri_decomposition: true,
41 include_literal_types: true,
42 graph_context: true,
43 namespace_aware: true,
44 default_metric: SimilarityMetric::Cosine,
45 cache_size: 10000,
46 }
47 }
48}
49
50#[derive(Debug, Clone, Serialize, Deserialize)]
52pub struct RdfTermMapping {
53 pub term: Term,
55 pub vector_id: VectorId,
57 pub graph_context: Option<GraphName>,
59 pub metadata: RdfTermMetadata,
61}
62
63#[derive(Debug, Clone, Serialize, Deserialize)]
65pub struct RdfTermMetadata {
66 pub term_type: RdfTermType,
68 pub namespace: Option<String>,
70 pub local_name: Option<String>,
72 pub datatype: Option<NamedNode>,
74 pub language: Option<String>,
76 pub complexity_score: f32,
78}
79
80#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
82pub enum RdfTermType {
83 NamedNode,
84 BlankNode,
85 Literal,
86 Variable,
87 QuotedTriple,
88}
89
90#[derive(Debug, Clone, Serialize, Deserialize)]
92pub struct RdfVectorSearchResult {
93 pub term: Term,
95 pub score: f32,
97 pub vector_id: VectorId,
99 pub graph_context: Option<GraphName>,
101 pub metadata: SearchMetadata,
103}
104
105#[derive(Debug, Clone, Serialize, Deserialize)]
107pub struct SearchMetadata {
108 pub algorithm: String,
110 pub processing_time_us: u64,
112 pub confidence: f32,
114 pub explanation: Option<String>,
116}
117
118pub struct RdfVectorIntegration {
120 config: RdfVectorConfig,
122 term_mappings: Arc<RwLock<HashMap<TermHash, RdfTermMapping>>>,
124 vector_mappings: Arc<RwLock<HashMap<VectorId, RdfTermMapping>>>,
126 graph_cache: Arc<RwLock<HashMap<GraphName, HashSet<VectorId>>>>,
128 namespace_registry: Arc<RwLock<HashMap<String, String>>>,
130 vector_store: Arc<RwLock<dyn VectorStoreTrait>>,
132}
133
134#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
136struct TermHash(u64);
137
138impl TermHash {
139 fn from_term(term: &Term) -> Self {
140 use std::collections::hash_map::DefaultHasher;
141 let mut hasher = DefaultHasher::new();
142
143 match term {
144 Term::NamedNode(node) => {
145 "NamedNode".hash(&mut hasher);
146 node.as_str().hash(&mut hasher);
147 }
148 Term::BlankNode(node) => {
149 "BlankNode".hash(&mut hasher);
150 node.as_str().hash(&mut hasher);
151 }
152 Term::Literal(literal) => {
153 "Literal".hash(&mut hasher);
154 literal.value().hash(&mut hasher);
155 if let Some(lang) = literal.language() {
156 lang.hash(&mut hasher);
157 }
158 literal.datatype().as_str().hash(&mut hasher);
159 }
160 Term::Variable(var) => {
161 "Variable".hash(&mut hasher);
162 var.as_str().hash(&mut hasher);
163 }
164 Term::QuotedTriple(_) => {
165 "QuotedTriple".hash(&mut hasher);
166 "quoted_triple".hash(&mut hasher);
168 }
169 }
170
171 TermHash(hasher.finish())
172 }
173}
174
175impl RdfVectorIntegration {
176 pub fn new(config: RdfVectorConfig, vector_store: Arc<RwLock<dyn VectorStoreTrait>>) -> Self {
178 Self {
179 config,
180 term_mappings: Arc::new(RwLock::new(HashMap::new())),
181 vector_mappings: Arc::new(RwLock::new(HashMap::new())),
182 graph_cache: Arc::new(RwLock::new(HashMap::new())),
183 namespace_registry: Arc::new(RwLock::new(HashMap::new())),
184 vector_store,
185 }
186 }
187
188 pub fn register_term(
190 &self,
191 term: Term,
192 vector: Vector,
193 graph_context: Option<GraphName>,
194 ) -> Result<VectorId> {
195 let vector_id = self.vector_store.write().add_vector(vector)?;
196 let metadata = self.extract_term_metadata(&term)?;
197
198 let mapping = RdfTermMapping {
199 term: term.clone(),
200 vector_id: vector_id.clone(),
201 graph_context: graph_context.clone(),
202 metadata,
203 };
204
205 let term_hash = TermHash::from_term(&term);
206
207 {
209 let mut term_mappings = self.term_mappings.write();
210 term_mappings.insert(term_hash, mapping.clone());
211 }
212
213 {
214 let mut vector_mappings = self.vector_mappings.write();
215 vector_mappings.insert(vector_id.clone(), mapping);
216 }
217
218 if let Some(graph) = graph_context {
220 let mut graph_cache = self.graph_cache.write();
221 graph_cache
222 .entry(graph)
223 .or_default()
224 .insert(vector_id.clone());
225 }
226
227 Ok(vector_id)
228 }
229
230 pub fn find_similar_terms(
232 &self,
233 query_term: &Term,
234 limit: usize,
235 threshold: Option<f32>,
236 graph_context: Option<&GraphName>,
237 ) -> Result<Vec<RdfVectorSearchResult>> {
238 let start_time = std::time::Instant::now();
239
240 let query_vector_id = self
242 .get_vector_id(query_term)?
243 .ok_or_else(|| anyhow!("Query term not found in vector store"))?;
244
245 let query_vector = self
246 .vector_store
247 .read()
248 .get_vector(&query_vector_id)?
249 .ok_or_else(|| anyhow!("Query vector not found"))?;
250
251 let candidate_vectors = if let Some(graph) = graph_context {
253 let graph_cache = self.graph_cache.read();
254 graph_cache
255 .get(graph)
256 .map(|set| set.iter().cloned().collect::<Vec<_>>())
257 .unwrap_or_default()
258 } else {
259 self.vector_store.read().get_all_vector_ids()?
261 };
262
263 let mut results = Vec::new();
265 for vector_id in candidate_vectors {
266 if *vector_id == query_vector_id {
267 continue; }
269
270 if let Ok(Some(vector)) = self.vector_store.read().get_vector(&vector_id) {
271 let similarity = self.config.default_metric.compute(&query_vector, &vector)?;
272
273 if let Some(thresh) = threshold {
275 if similarity < thresh {
276 continue;
277 }
278 }
279
280 let vector_mappings = self.vector_mappings.read();
282 if let Some(mapping) = vector_mappings.get(&vector_id) {
283 let processing_time = start_time.elapsed().as_micros() as u64;
284
285 results.push(RdfVectorSearchResult {
286 term: mapping.term.clone(),
287 score: similarity,
288 vector_id: vector_id.clone(),
289 graph_context: mapping.graph_context.clone(),
290 metadata: SearchMetadata {
291 algorithm: "vector_similarity".to_string(),
292 processing_time_us: processing_time,
293 confidence: self.calculate_confidence(similarity, &mapping.metadata),
294 explanation: self.generate_explanation(&mapping.metadata, similarity),
295 },
296 });
297 }
298 }
299 }
300
301 results.sort_by(|a, b| {
303 b.score
304 .partial_cmp(&a.score)
305 .unwrap_or(std::cmp::Ordering::Equal)
306 });
307
308 results.truncate(limit);
310
311 Ok(results)
312 }
313
314 pub fn search_by_text(
316 &self,
317 query_text: &str,
318 limit: usize,
319 threshold: Option<f32>,
320 graph_context: Option<&GraphName>,
321 ) -> Result<Vec<RdfVectorSearchResult>> {
322 let literal = Literal::new_simple_literal(query_text);
324 let _query_term = Term::Literal(literal);
325
326 let query_vector = self.generate_text_embedding(query_text)?;
329
330 let temp_vector_id = self.vector_store.write().add_vector(query_vector.clone())?;
332
333 let candidate_vectors = if let Some(graph) = graph_context {
335 let graph_cache = self.graph_cache.read();
336 graph_cache
337 .get(graph)
338 .map(|set| set.iter().cloned().collect::<Vec<_>>())
339 .unwrap_or_default()
340 } else {
341 self.vector_store.read().get_all_vector_ids()?
342 };
343
344 let mut results = Vec::new();
345 let start_time = std::time::Instant::now();
346
347 for vector_id in candidate_vectors {
348 if let Ok(Some(vector)) = self.vector_store.read().get_vector(&vector_id) {
349 let similarity = self.config.default_metric.compute(&query_vector, &vector)?;
350
351 if let Some(thresh) = threshold {
352 if similarity < thresh {
353 continue;
354 }
355 }
356
357 let vector_mappings = self.vector_mappings.read();
358 if let Some(mapping) = vector_mappings.get(&vector_id) {
359 let processing_time = start_time.elapsed().as_micros() as u64;
360
361 results.push(RdfVectorSearchResult {
362 term: mapping.term.clone(),
363 score: similarity,
364 vector_id: vector_id.clone(),
365 graph_context: mapping.graph_context.clone(),
366 metadata: SearchMetadata {
367 algorithm: "text_similarity".to_string(),
368 processing_time_us: processing_time,
369 confidence: self.calculate_confidence(similarity, &mapping.metadata),
370 explanation: Some(format!("Text similarity match: '{query_text}'")),
371 },
372 });
373 }
374 }
375 }
376
377 let _ = self.vector_store.write().remove_vector(&temp_vector_id);
379
380 results.sort_by(|a, b| {
382 b.score
383 .partial_cmp(&a.score)
384 .unwrap_or(std::cmp::Ordering::Equal)
385 });
386 results.truncate(limit);
387
388 Ok(results)
389 }
390
391 pub fn get_vector_id(&self, term: &Term) -> Result<Option<VectorId>> {
393 let term_hash = TermHash::from_term(term);
394 let term_mappings = self.term_mappings.read();
395 Ok(term_mappings
396 .get(&term_hash)
397 .map(|mapping| mapping.vector_id.clone()))
398 }
399
400 pub fn get_term(&self, vector_id: VectorId) -> Result<Option<Term>> {
402 let vector_mappings = self.vector_mappings.read();
403 Ok(vector_mappings
404 .get(&vector_id)
405 .map(|mapping| mapping.term.clone()))
406 }
407
408 pub fn register_namespace(&self, prefix: String, uri: String) -> Result<()> {
410 let mut registry = self.namespace_registry.write();
411 registry.insert(prefix, uri);
412 Ok(())
413 }
414
415 fn extract_term_metadata(&self, term: &Term) -> Result<RdfTermMetadata> {
417 match term {
418 Term::NamedNode(node) => {
419 let uri = node.as_str();
420 let (namespace, local_name) = self.split_uri(uri);
421
422 Ok(RdfTermMetadata {
423 term_type: RdfTermType::NamedNode,
424 namespace,
425 local_name,
426 datatype: None,
427 language: None,
428 complexity_score: self.calculate_uri_complexity(uri),
429 })
430 }
431 Term::BlankNode(_) => {
432 Ok(RdfTermMetadata {
433 term_type: RdfTermType::BlankNode,
434 namespace: None,
435 local_name: None,
436 datatype: None,
437 language: None,
438 complexity_score: 0.5, })
440 }
441 Term::Literal(literal) => Ok(RdfTermMetadata {
442 term_type: RdfTermType::Literal,
443 namespace: None,
444 local_name: None,
445 datatype: Some(literal.datatype().into()),
446 language: literal.language().map(|s| s.to_string()),
447 complexity_score: self.calculate_literal_complexity(literal),
448 }),
449 Term::Variable(_) => {
450 Ok(RdfTermMetadata {
451 term_type: RdfTermType::Variable,
452 namespace: None,
453 local_name: None,
454 datatype: None,
455 language: None,
456 complexity_score: 0.3, })
458 }
459 Term::QuotedTriple(_) => {
460 Ok(RdfTermMetadata {
461 term_type: RdfTermType::QuotedTriple,
462 namespace: None,
463 local_name: None,
464 datatype: None,
465 language: None,
466 complexity_score: 1.0, })
468 }
469 }
470 }
471
472 fn split_uri(&self, uri: &str) -> (Option<String>, Option<String>) {
474 if let Some(pos) = uri.rfind(&['#', '/'][..]) {
476 let namespace = uri[..pos + 1].to_string();
477 let local_name = uri[pos + 1..].to_string();
478 (Some(namespace), Some(local_name))
479 } else {
480 (None, Some(uri.to_string()))
481 }
482 }
483
484 fn calculate_uri_complexity(&self, uri: &str) -> f32 {
486 let length_factor = (uri.len() as f32 / 100.0).min(1.0);
487 let segment_count = uri.matches(&['/', '#'][..]).count() as f32 / 10.0;
488 let query_params = if uri.contains('?') { 0.2 } else { 0.0 };
489
490 (length_factor + segment_count + query_params).min(1.0)
491 }
492
493 fn calculate_literal_complexity(&self, literal: &Literal) -> f32 {
495 let value_length = literal.value().len() as f32 / 200.0;
496 let datatype_complexity =
497 if literal.datatype().as_str() == "http://www.w3.org/2001/XMLSchema#string" {
498 0.3
499 } else {
500 0.7
501 };
502 let language_bonus = if literal.language().is_some() {
503 0.2
504 } else {
505 0.0
506 };
507
508 (value_length + datatype_complexity + language_bonus).min(1.0)
509 }
510
511 fn calculate_confidence(&self, similarity: f32, metadata: &RdfTermMetadata) -> f32 {
513 let base_confidence = similarity;
514 let complexity_bonus = metadata.complexity_score * 0.1;
515 let type_bonus = match metadata.term_type {
516 RdfTermType::NamedNode => 0.1,
517 RdfTermType::Literal => 0.05,
518 RdfTermType::BlankNode => 0.02,
519 RdfTermType::Variable => 0.01,
520 RdfTermType::QuotedTriple => 0.15,
521 };
522
523 (base_confidence + complexity_bonus + type_bonus).min(1.0)
524 }
525
526 fn generate_explanation(&self, metadata: &RdfTermMetadata, similarity: f32) -> Option<String> {
528 let term_type_str = match metadata.term_type {
529 RdfTermType::NamedNode => "Named Node",
530 RdfTermType::BlankNode => "Blank Node",
531 RdfTermType::Literal => "Literal",
532 RdfTermType::Variable => "Variable",
533 RdfTermType::QuotedTriple => "Quoted Triple",
534 };
535
536 let mut explanation = format!(
537 "{} with {:.2}% similarity",
538 term_type_str,
539 similarity * 100.0
540 );
541
542 if let Some(namespace) = &metadata.namespace {
543 explanation.push_str(&format!(", namespace: {namespace}"));
544 }
545
546 if let Some(language) = &metadata.language {
547 explanation.push_str(&format!(", language: {language}"));
548 }
549
550 Some(explanation)
551 }
552
553 fn generate_text_embedding(&self, text: &str) -> Result<Vector> {
555 let words: Vec<&str> = text.split_whitespace().collect();
558 let dimension = 384; let mut vector_data = vec![0.0; dimension];
561
562 for word in words.iter() {
564 let word_hash = {
565 use std::collections::hash_map::DefaultHasher;
566 let mut hasher = DefaultHasher::new();
567 word.hash(&mut hasher);
568 hasher.finish()
569 };
570
571 for j in 0..dimension {
573 let index = (word_hash as usize + j) % dimension;
574 vector_data[index] += 1.0 / (words.len() as f32);
575 }
576 }
577
578 let norm: f32 = vector_data.iter().map(|x| x * x).sum::<f32>().sqrt();
580 if norm > 0.0 {
581 for value in &mut vector_data {
582 *value /= norm;
583 }
584 }
585
586 Ok(Vector::new(vector_data))
587 }
588
589 pub fn get_statistics(&self) -> RdfIntegrationStats {
591 let term_mappings = self.term_mappings.read();
592 let graph_cache = self.graph_cache.read();
593 let namespace_registry = self.namespace_registry.read();
594
595 let mut type_counts = HashMap::new();
596 for mapping in term_mappings.values() {
597 *type_counts.entry(mapping.metadata.term_type).or_insert(0) += 1;
598 }
599
600 RdfIntegrationStats {
601 total_terms: term_mappings.len(),
602 total_graphs: graph_cache.len(),
603 total_namespaces: namespace_registry.len(),
604 type_distribution: type_counts,
605 cache_hit_ratio: 0.95, }
607 }
608}
609
610#[derive(Debug, Clone, Serialize, Deserialize)]
612pub struct RdfIntegrationStats {
613 pub total_terms: usize,
614 pub total_graphs: usize,
615 pub total_namespaces: usize,
616 pub type_distribution: HashMap<RdfTermType, usize>,
617 pub cache_hit_ratio: f32,
618}
619
620#[cfg(test)]
621mod tests {
622 use super::*;
623 use crate::VectorStore;
624 use anyhow::Result;
625 use oxirs_core::model::{NamedNode, Term};
626
627 #[test]
628 fn test_rdf_term_registration() -> Result<()> {
629 let config = RdfVectorConfig::default();
630 let vector_store = Arc::new(RwLock::new(VectorStore::new()));
631 let integration = RdfVectorIntegration::new(config, vector_store);
632
633 let named_node = NamedNode::new("http://example.org/person")?;
634 let term = Term::NamedNode(named_node);
635 let vector = Vector::new(vec![1.0, 0.0, 0.0]);
636
637 let vector_id = integration.register_term(term.clone(), vector, None)?;
638
639 assert!(integration
640 .get_vector_id(&term)
641 .expect("test value")
642 .is_some());
643 assert_eq!(
644 integration
645 .get_vector_id(&term)
646 .expect("get_vector_id should return Some")
647 .expect("inner Option should be Some"),
648 vector_id
649 );
650 Ok(())
651 }
652
653 #[test]
654 fn test_uri_splitting() {
655 let config = RdfVectorConfig::default();
656 let vector_store = Arc::new(RwLock::new(VectorStore::new()));
657 let integration = RdfVectorIntegration::new(config, vector_store);
658
659 let (namespace, local_name) = integration.split_uri("http://example.org/ontology#Person");
660 assert_eq!(namespace, Some("http://example.org/ontology#".to_string()));
661 assert_eq!(local_name, Some("Person".to_string()));
662 }
663
664 #[test]
671 fn test_lock_survives_panic_while_held() -> Result<()> {
672 let config = RdfVectorConfig::default();
673 let vector_store = Arc::new(RwLock::new(VectorStore::new()));
674 let integration = Arc::new(RdfVectorIntegration::new(config, vector_store));
675
676 let integration_clone = integration.clone();
677 let handle = std::thread::spawn(move || {
678 let _guard = integration_clone.term_mappings.write();
681 panic!("simulated panic while holding the lock");
682 });
683 assert!(handle.join().is_err());
686
687 let named_node = NamedNode::new("http://example.org/after-panic")?;
690 let term = Term::NamedNode(named_node);
691 let vector = Vector::new(vec![1.0, 0.0, 0.0]);
692 let vector_id = integration.register_term(term.clone(), vector, None)?;
693 assert_eq!(
694 integration
695 .get_vector_id(&term)
696 .expect("get_vector_id should not panic after another thread's panic")
697 .expect("vector id should be present"),
698 vector_id
699 );
700 Ok(())
701 }
702
703 #[test]
704 fn test_metadata_extraction() -> Result<()> {
705 let config = RdfVectorConfig::default();
706 let vector_store = Arc::new(RwLock::new(VectorStore::new()));
707 let integration = RdfVectorIntegration::new(config, vector_store);
708
709 let literal = Literal::new_language_tagged_literal("Hello", "en")?;
710 let term = Term::Literal(literal);
711
712 let metadata = integration.extract_term_metadata(&term)?;
713 assert_eq!(metadata.term_type, RdfTermType::Literal);
714 assert_eq!(metadata.language, Some("en".to_string()));
715 Ok(())
716 }
717}