Skip to main content

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(&current, 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(&current, 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}