rudof_rdf/rdf_core/neighs_rdf.rs
1use crate::rdf_core::vocabs::RdfVocab;
2use crate::rdf_core::{
3 Any, Matcher, RDFError, Rdf, SHACLPath,
4 term::{Object, Triple},
5};
6use std::{
7 collections::{HashMap, HashSet},
8 vec::IntoIter,
9};
10//----------------------------------------------------------------
11// Type aliases for common RDF navigation patterns
12//----------------------------------------------------------------
13
14/// Maps predicates to sets of subjects (inverse navigation)
15pub type IncomingArcs<R> = HashMap<<R as Rdf>::IRI, HashSet<<R as Rdf>::Subject>>;
16/// Maps predicates to sets of objects (forward navigation)
17pub type OutgoingArcs<R> = HashMap<<R as Rdf>::IRI, HashSet<<R as Rdf>::Term>>;
18/// Filtered outgoing arcs with reminder predicates
19pub type OutgoingArcsFromList<R> = (OutgoingArcs<R>, Vec<<R as Rdf>::IRI>);
20
21/// Trait for navigating RDF graphs and querying triples.
22///
23/// This trait extends [`Rdf`] with methods for retrieving triples based on
24/// subject-predicate-object patterns, exploring node neighborhoods, and
25/// following SHACL property paths. All query methods support flexible
26/// matching using the [`Matcher`] trait, allowing exact matches or wildcards.
27///
28/// # Graph Navigation
29///
30/// The trait provides two primary navigation models:
31///
32/// - **Triple queries**: Retrieve triples matching specific patterns
33/// - **Arc-based navigation**: Explore incoming and outgoing relationships
34pub trait NeighsRDF: Rdf {
35 /// Returns an iterator over all triples in the RDF graph.
36 ///
37 /// This method provides access to the complete set of triples. For large
38 /// graphs, implementations should return a lazy iterator that retrieves
39 /// triples incrementally rather than loading everything into memory.
40 fn triples(&self) -> Result<impl Iterator<Item = Self::Triple>, Self::Err>;
41
42 /// Checks whether the graph contains at least one triple matching the pattern.
43 ///
44 /// # Arguments
45 ///
46 /// * `subject` - Matcher for the subject (use [`Any`] for wildcard)
47 /// * `predicate` - Matcher for the predicate (use [`Any`] for wildcard)
48 /// * `object` - Matcher for the object (use [`Any`] for wildcard)
49 fn contains<S, P, O>(&self, subject: &S, predicate: &P, object: &O) -> Result<bool, Self::Err>
50 where
51 S: Matcher<Self::Subject>,
52 P: Matcher<Self::IRI>,
53 O: Matcher<Self::Term>,
54 {
55 let mut iter = self.triples_matching(subject, predicate, object)?;
56 Ok(iter.next().is_some())
57 }
58
59 /// Returns an iterator over triples matching the given pattern.
60 ///
61 /// This is the core query method that all other triple queries delegate to.
62 /// Each parameter accepts a [`Matcher`], allowing exact values or wildcards
63 /// via [`Any`].
64 ///
65 /// # Implementation Note
66 ///
67 /// This function must retrieve triples from the graph, but should **not**
68 /// load all triples into memory for large graphs. For SPARQL-based
69 /// implementations, translate the pattern into a SPARQL query that
70 /// retrieves only matching triples incrementally.
71 ///
72 /// # Arguments
73 ///
74 /// * `subject` - Matcher for the subject position
75 /// * `predicate` - Matcher for the predicate position
76 /// * `object` - Matcher for the object position
77 fn triples_matching<S, P, O>(
78 &self,
79 subject: &S,
80 predicate: &P,
81 object: &O,
82 ) -> Result<impl Iterator<Item = Self::Triple> + '_, Self::Err>
83 where
84 S: Matcher<Self::Subject>,
85 P: Matcher<Self::IRI>,
86 O: Matcher<Self::Term>;
87
88 /// Returns all triples with the specified subject.
89 ///
90 /// Equivalent to `triples_matching(subject, Any, Any)`.
91 ///
92 /// # Arguments
93 ///
94 /// * `subject` - The subject to match
95 fn triples_with_subject(
96 &self,
97 subject: &Self::Subject,
98 ) -> Result<impl Iterator<Item = Self::Triple> + '_, Self::Err> {
99 self.triples_matching(subject, &Any, &Any)
100 }
101
102 /// Returns all triples with the specified subject and predicate.
103 ///
104 /// Equivalent to `triples_matching(subject, predicate, Any)`.
105 ///
106 /// # Arguments
107 ///
108 /// * `subject` - The subject to match
109 /// * `predicate` - The predicate to match
110 fn triples_with_subject_predicate(
111 &self,
112 subject: &Self::Subject,
113 predicate: &Self::IRI,
114 ) -> Result<impl Iterator<Item = Self::Triple> + '_, Self::Err> {
115 self.triples_matching(subject, predicate, &Any)
116 }
117
118 /// Returns all triples with the specified predicate.
119 ///
120 /// Equivalent to `triples_matching(Any, predicate, Any)`.
121 ///
122 /// # Arguments
123 ///
124 /// * `predicate` - The predicate to match
125 fn triples_with_predicate(
126 &self,
127 predicate: &Self::IRI,
128 ) -> Result<impl Iterator<Item = Self::Triple> + '_, Self::Err> {
129 self.triples_matching(&Any, predicate, &Any)
130 }
131
132 /// Returns all triples with the specified predicate and object.
133 ///
134 /// Equivalent to `triples_matching(Any, predicate, object)`.
135 ///
136 /// # Arguments
137 ///
138 /// * `predicate` - The predicate to match
139 /// * `object` - The object to match
140 fn triples_with_predicate_object(
141 &self,
142 predicate: &Self::IRI,
143 object: &Self::Term,
144 ) -> Result<impl Iterator<Item = Self::Triple> + '_, Self::Err> {
145 self.triples_matching(&Any, predicate, object)
146 }
147
148 /// Returns all triples with the specified object.
149 ///
150 /// Equivalent to `triples_matching(Any, Any, object)`.
151 ///
152 /// # Arguments
153 ///
154 /// * `object` - The object to match
155 fn triples_with_object(&self, object: &Self::Term) -> Result<impl Iterator<Item = Self::Triple> + '_, Self::Err> {
156 self.triples_matching(&Any, &Any, object)
157 }
158
159 /// Returns all incoming arcs (predicates and subjects) pointing to an object.
160 ///
161 /// This method performs reverse navigation, finding all subjects that have
162 /// relationships pointing to the specified object, grouped by predicate.
163 ///
164 /// # Arguments
165 ///
166 /// * `object` - The object term to find incoming relationships for
167 fn incoming_arcs(&self, object: &Self::Term) -> Result<IncomingArcs<Self>, Self::Err> {
168 let mut results = IncomingArcs::<Self>::new();
169 for triple in self.triples_with_object(object)? {
170 let (s, p, _) = triple.into_components();
171 results.entry(p).or_default().insert(s);
172 }
173 Ok(results)
174 }
175
176 /// Returns all outgoing arcs (predicates and objects) from a subject.
177 ///
178 /// This method performs forward navigation, finding all predicates and
179 /// their associated objects for the specified subject.
180 ///
181 /// # Arguments
182 ///
183 /// * `subject` - The subject to find outgoing relationships for
184 fn outgoing_arcs(&self, subject: &Self::Subject) -> Result<OutgoingArcs<Self>, Self::Err> {
185 let mut results = OutgoingArcs::<Self>::new();
186 for triple in self.triples_with_subject(subject)? {
187 let (_, p, o) = triple.into_components();
188 results.entry(p).or_default().insert(o);
189 }
190 Ok(results)
191 }
192
193 /// Returns filtered outgoing arcs and remainder predicates.
194 ///
195 /// This method retrieves outgoing arcs from a subject, but only includes
196 /// predicates that appear in the provided allowlist. Predicates not in
197 /// the list are collected separately in the remainder vector.
198 ///
199 /// # Arguments
200 ///
201 /// * `subject` - The subject to query
202 /// * `preds` - A slice of predicates to include in the filtered results
203 fn outgoing_arcs_from_list(
204 &self,
205 subject: &Self::Subject,
206 preds: &[Self::IRI],
207 ) -> Result<OutgoingArcsFromList<Self>, Self::Err> {
208 let mut results = OutgoingArcs::<Self>::new();
209 let mut reminder = Vec::new();
210
211 for triple in self.triples_with_subject(subject)? {
212 let (_, p, o) = triple.into_components();
213
214 if preds.contains(&p) {
215 results.entry(p).or_default().insert(o);
216 } else {
217 reminder.push(p);
218 }
219 }
220
221 Ok((results, reminder))
222 }
223
224 /// Returns all subjects that are instances of the specified class.
225 ///
226 /// This method queries for subjects that have `rdf:type` relationships
227 /// pointing to the given class term.
228 ///
229 /// # Arguments
230 ///
231 /// * `cls` - Matcher for the class (object position of `rdf:type` triples)
232 fn shacl_instances_of<O>(&self, cls: &O) -> Result<impl Iterator<Item = Self::Subject>, Self::Err>
233 where
234 O: Matcher<Self::Term>,
235 {
236 let rdf_type: Self::IRI = RdfVocab::rdf_type().clone().into();
237 let subjects: HashSet<_> = self
238 .triples_matching(&Any, &rdf_type, cls)?
239 .map(Triple::into_subject)
240 .collect();
241 Ok(subjects.into_iter())
242 }
243
244 /// Returns all subjects that reify the specified triple.
245 ///
246 /// This method finds RDF reification statements where subjects use
247 /// `rdf:reifies` to reference the given triple. This supports RDF-star
248 /// reification patterns.
249 ///
250 /// # Arguments
251 ///
252 /// * `triple` - The triple to find reifiers for
253 fn reifiers_of_triple(&self, triple: &Self::Triple) -> Result<impl Iterator<Item = Self::Subject>, Self::Err> {
254 let triple_term = Self::triple_as_term(triple);
255 let rdf_reifies: Self::IRI = RdfVocab::rdf_reifies().clone().into();
256 let reifiers = Self::triples_with_predicate_object(self, &rdf_reifies, &triple_term)?
257 .map(|t| t.into_subject())
258 .collect::<HashSet<_>>();
259 // Find x such that: x rdf:reifies <<( s p o )>>
260 Ok(reifiers.into_iter())
261 }
262
263 /// Returns the first object for the given subject-predicate pair.
264 ///
265 /// This is a convenience method that returns at most one object. If multiple
266 /// objects exist, only the first encountered is returned.
267 ///
268 /// # Arguments
269 ///
270 /// * `subject` - The subject to query
271 /// * `predicate` - The predicate to match
272 fn object_for(&self, subject: &Self::Term, predicate: &Self::IRI) -> Result<Option<Object>, RDFError> {
273 match self.objects_for(subject, predicate)?.into_iter().next() {
274 Some(term) => {
275 let obj = Self::term_as_object(&term)?;
276 Ok(Some(obj))
277 },
278 None => Ok(None),
279 }
280 }
281
282 /// Returns all objects reachable by following a SHACL property path.
283 ///
284 /// SHACL property paths extend simple predicate-based navigation with
285 /// complex path expressions including sequences, alternatives, inverses,
286 /// and quantifiers.
287 ///
288 /// # Path Types
289 ///
290 /// - **Predicate**: Direct predicate navigation (`ex:name`)
291 /// - **Alternative**: Union of multiple paths (`ex:father | ex:mother`)
292 /// - **Sequence**: Composed paths (`ex:parent / ex:name`)
293 /// - **Inverse**: Reverse navigation (`^ex:author`)
294 /// - **ZeroOrMore**: Transitive closure (`ex:subClassOf*`)
295 /// - **OneOrMore**: Non-empty transitive closure (`ex:subClassOf+`)
296 /// - **ZeroOrOne**: Optional path (`ex:nickname?`)
297 ///
298 /// # Arguments
299 ///
300 /// * `subject` - The starting term for path navigation
301 /// * `path` - The SHACL property path to follow
302 fn objects_for_shacl_path(&self, subject: &Self::Term, path: &SHACLPath) -> Result<HashSet<Self::Term>, RDFError> {
303 match path {
304 SHACLPath::Predicate { pred } => {
305 let pred: Self::IRI = pred.clone().into();
306 self.objects_for(subject, &pred)
307 },
308 SHACLPath::Alternative { paths } => {
309 let mut all_objects = HashSet::new();
310 for path in paths {
311 let objects = self.objects_for_shacl_path(subject, path)?;
312 all_objects.extend(objects);
313 }
314 Ok(all_objects)
315 },
316 SHACLPath::Sequence { paths } => match paths.as_slice() {
317 [] => Ok(HashSet::from([subject.clone()])),
318 [first, rest @ ..] => {
319 let first_objects = self.objects_for_shacl_path(subject, first)?;
320 let mut all_objects = HashSet::new();
321 for obj in first_objects {
322 let intermediate_objects =
323 self.objects_for_shacl_path(&obj, &SHACLPath::Sequence { paths: rest.to_vec() })?;
324 all_objects.extend(intermediate_objects);
325 }
326 Ok(all_objects)
327 },
328 },
329 SHACLPath::Inverse { path } => {
330 let pred: Self::IRI = path.pred().unwrap().clone().into();
331 let objects = self.subjects_for(&pred, subject)?;
332 Ok(objects)
333 },
334 SHACLPath::ZeroOrMore { path } => {
335 let mut all_objects = HashSet::new();
336 all_objects.insert(subject.clone());
337
338 let mut to_process = vec![subject.clone()];
339 while let Some(current) = to_process.pop() {
340 let next_objects = self.objects_for_shacl_path(¤t, path)?;
341 for obj in next_objects {
342 if all_objects.insert(obj.clone()) {
343 to_process.push(obj);
344 }
345 }
346 }
347 Ok(all_objects)
348 },
349 SHACLPath::OneOrMore { path } => {
350 let mut all_objects = HashSet::new();
351 let first_objects = self.objects_for_shacl_path(subject, path)?;
352 all_objects.extend(first_objects.clone());
353
354 let mut to_process: Vec<Self::Term> = first_objects.into_iter().collect();
355 while let Some(current) = to_process.pop() {
356 let next_objects = self.objects_for_shacl_path(¤t, path)?;
357 for obj in next_objects {
358 if all_objects.insert(obj.clone()) {
359 to_process.push(obj);
360 }
361 }
362 }
363 Ok(all_objects)
364 },
365 SHACLPath::ZeroOrOne { path } => {
366 let mut all_objects = HashSet::new();
367 all_objects.insert(subject.clone());
368 let next_objects = self.objects_for_shacl_path(subject, path)?;
369 all_objects.extend(next_objects);
370 Ok(all_objects)
371 },
372 }
373 }
374
375 /// Returns all objects for the given subject-predicate pair.
376 ///
377 /// This method retrieves the object position of all triples matching
378 /// the specified subject and predicate.
379 ///
380 /// # Arguments
381 ///
382 /// * `subject` - The subject term to query
383 /// * `predicate` - The predicate IRI to match
384 ///
385 /// # Errors
386 ///
387 /// Returns [`RDFError::ErrorObjectsFor`] if the query fails or if the
388 /// subject term cannot be converted to a valid subject.
389 fn objects_for(&self, subject: &Self::Term, predicate: &Self::IRI) -> Result<HashSet<Self::Term>, RDFError> {
390 let subject_node: Self::Subject = Self::term_as_subject(subject)?;
391 let subject_str = format!("{subject}");
392 let predicate_str = format!("{predicate}");
393 let triples = self
394 .triples_matching(&subject_node, predicate, &Any)
395 .map_err(|e| RDFError::ErrorObjectsFor {
396 subject: subject_str,
397 predicate: predicate_str,
398 error: e.to_string(),
399 })?
400 .map(Triple::into_object)
401 .collect();
402
403 Ok(triples)
404 }
405
406 /// Returns all subjects for the given predicate-object pair.
407 ///
408 /// This method performs reverse lookup, finding subjects that have the
409 /// specified predicate pointing to the given object.
410 ///
411 /// # Arguments
412 ///
413 /// * `predicate` - The predicate IRI to match
414 /// * `object` - The object term to query
415 ///
416 /// # Errors
417 ///
418 /// Returns [`RDFError::ErrorSubjectsFor`] if the query fails.
419 fn subjects_for(&self, predicate: &Self::IRI, object: &Self::Term) -> Result<HashSet<Self::Term>, RDFError> {
420 let values = self
421 .triples_matching(&Any, predicate, object)
422 .map_err(|e| RDFError::ErrorSubjectsFor {
423 predicate: format!("{predicate}"),
424 object: format!("{object}"),
425 error: e.to_string(),
426 })?
427 .map(Triple::into_subject)
428 .map(Into::into)
429 .collect();
430 Ok(values)
431 }
432}
433
434/// Represents a single neighborhood relationship in an RDF graph.
435///
436/// A neighborhood relationship can be either a direct connection (outgoing arc)
437/// or an inverse connection (incoming arc) relative to a focus node.
438///
439/// # Type Parameters
440///
441/// * `S` - The RDF graph type implementing [`NeighsRDF`]
442///
443/// # Variants
444///
445/// - [`Direct`](Self::Direct): An outgoing relationship where the focus node is the subject
446/// - [`Inverse`](Self::Inverse): An incoming relationship where the focus node is the object
447pub enum Neigh<S>
448where
449 S: NeighsRDF,
450{
451 /// A direct (outgoing) relationship from the focus node.
452 ///
453 /// Represents a triple pattern: `(focusNode, p, o)` where the focus node
454 /// is the subject, `p` is the predicate, and `o` is the object.
455 ///
456 /// # Fields
457 ///
458 /// * `p` - The predicate IRI of the relationship
459 /// * `o` - The object term that the predicate points to
460 Direct { p: S::IRI, o: S::Term },
461 /// An inverse (incoming) relationship to the focus node.
462 ///
463 /// Represents a triple pattern: `(s, p, focusNode)` where `s` is the subject,
464 /// `p` is the predicate, and the focus node is the object.
465 ///
466 /// # Fields
467 ///
468 /// * `s` - The subject that has this relationship to the focus node
469 /// * `p` - The predicate IRI of the relationship
470 Inverse { s: S::Subject, p: S::IRI },
471}
472
473impl<S> Neigh<S>
474where
475 S: NeighsRDF,
476{
477 /// Creates a direct (outgoing) neighborhood relationship.
478 ///
479 /// Constructs a neighborhood representing an outgoing arc from a focus node
480 /// via the specified predicate to the given object.
481 ///
482 /// # Arguments
483 ///
484 /// * `pred` - The predicate IRI of the relationship
485 /// * `object` - The object term that the predicate points to
486 pub fn direct(pred: S::IRI, object: S::Term) -> Neigh<S> {
487 Neigh::Direct { p: pred, o: object }
488 }
489
490 /// Creates an inverse (incoming) neighborhood relationship.
491 ///
492 /// Constructs a neighborhood representing an incoming arc to a focus node
493 /// via the specified predicate from the given subject.
494 ///
495 /// # Arguments
496 ///
497 /// * `pred` - The predicate IRI of the relationship
498 /// * `subject` - The subject that has this relationship to the focus node
499 pub fn inverse(pred: S::IRI, subject: S::Subject) -> Neigh<S> {
500 Neigh::Inverse { p: pred, s: subject }
501 }
502}
503
504/// An iterator over the neighborhood of a node in an RDF graph.
505///
506/// This lazy iterator yields all direct (outgoing) and inverse (incoming) relationships
507/// for a given node in an RDF graph without materializing the entire neighborhood in memory at once.
508///
509/// # Type Parameters
510///
511/// * `S` - The RDF graph type implementing [`NeighsRDF`]
512pub struct NeighsIterator<S>
513where
514 S: NeighsRDF,
515{
516 /// The term whose neighborhood is being iterated over.
517 _term: S::Term,
518
519 /// Internal iterator over neighborhood relationships [`Neigh`].
520 _neigh_iter: IntoIter<Neigh<S>>,
521}
522
523impl<S> NeighsIterator<S>
524where
525 S: NeighsRDF,
526{
527 /// Creates a new neighborhood iterator for the given term.
528 ///
529 /// This method initializes an iterator that will traverse all direct and
530 /// inverse relationships of the specified term in the RDF graph.
531 ///
532 /// # Arguments
533 ///
534 /// * `term` - The RDF term whose neighborhood should be iterated
535 /// * `rdf` - The RDF graph to query for neighborhood relationships
536 pub fn new(term: S::Term, rdf: S) -> Result<NeighsIterator<S>, S::Err> {
537 match S::term_as_subject(&term) {
538 Ok(subject) => {
539 let subject: S::Subject = subject;
540 // Collect all predicates for this subject
541 let preds: HashSet<S::IRI> = rdf
542 .triples_with_subject(&subject)?
543 .map(Triple::into_predicate)
544 .collect();
545 let _qs = preds.into_iter();
546
547 // TODO: Complete implementation
548 // The intended approach is to:
549 // 1. For each predicate, get all objects (direct neighs)
550 // 2. Collect predicates where term appears as object (inverse neighs)
551 // 3. Create a lazy iterator that yields both types of neighs
552
553 /*let vv = qs.flat_map(|p| {
554 let objs = rdf.get_objects_for_subject_predicate(&subject, &p)?;
555 objs.into_iter().map(|o| Neigh::Direct { p, o })
556 });*/
557
558 todo!(); // Ok(vv)
559 },
560 Err(_) => {
561 // TODO: Handle case where term is not a subject
562 // Should still find inverse relationships where term appears as object
563 todo!()
564 },
565 }
566 }
567}
568
569impl<S> FromIterator<Neigh<S>> for NeighsIterator<S>
570where
571 S: NeighsRDF,
572{
573 /// Constructs a `NeighsIterator` from an iterator of neighborhoods.
574 fn from_iter<T>(_t: T) -> Self
575 where
576 T: IntoIterator,
577 {
578 todo!()
579 }
580}
581
582impl<S> Iterator for NeighsIterator<S>
583where
584 S: NeighsRDF,
585{
586 /// The neighborhood relationship type yielded by this iterator.
587 type Item = Neigh<S>;
588
589 /// Advances the iterator and returns the next neighborhood relationship.
590 ///
591 /// # Returns
592 ///
593 /// - `Some(Neigh<S>)` if there are more neighborhood relationships
594 /// - `None` if the iteration is complete
595 fn next(&mut self) -> Option<Self::Item> {
596 todo!()
597 }
598}