Skip to main content

rudof_rdf/rdf_impl/
in_memory_graph.rs

1use crate::rdf_core::{
2    AsyncRDF, BuildRDF, FocusRDF, Matcher, NeighsRDF, RDFFormat, Rdf,
3    query::{QueryRDF, QueryResultFormat, QuerySolution, QuerySolutions, VarName},
4};
5use crate::rdf_impl::in_memory_graph_error::InMemoryGraphError;
6
7use crate::rdf_core::vocabs::RdfVocab;
8use async_trait::async_trait;
9use colored::*;
10use iri_s::IriS;
11use oxjsonld::JsonLdParser;
12use oxrdf::{
13    BlankNode as OxBlankNode, Graph, GraphName, Literal as OxLiteral, NamedNode as OxNamedNode, NamedNodeRef,
14    NamedOrBlankNode as OxSubject, NamedOrBlankNodeRef as OxSubjectRef, Quad, Term as OxTerm, TermRef,
15    Triple as OxTriple, TripleRef,
16};
17use oxrdfio::{JsonLdProfileSet, RdfFormat, RdfSerializer};
18use oxrdfxml::RdfXmlParser;
19use oxttl::{NQuadsParser, NTriplesParser, TurtleParser};
20use prefixmap::{PrefixMapError, map::*};
21use serde::{Serialize, ser::SerializeStruct};
22use std::collections::{HashMap, HashSet};
23use std::sync::Arc;
24use std::{
25    fmt::{Debug, Display, Formatter},
26    io::{self, BufReader, Cursor, Write},
27    str::FromStr,
28};
29#[cfg(not(target_family = "wasm"))]
30use std::{fs::File, path::Path};
31#[cfg(feature = "sparql")]
32use {
33    oxigraph::{
34        sparql::{QueryResults, SparqlEvaluator},
35        store::Store,
36    },
37    sparesults::QuerySolution as SparQuerySolution,
38};
39
40/// An RDF graph stored entirely in memory.
41///
42/// The graph is backed by [`oxrdf::Graph`] and enriched with prefix handling,
43/// base IRI support, blank node generation, and optional SPARQL querying via
44/// an Oxigraph [`Store`].
45#[derive(Default, Clone)]
46pub struct InMemoryGraph {
47    /// Optional focus term used by [`FocusRDF`] operations.
48    focus: Option<OxTerm>,
49
50    /// Underlying RDF graph.
51    graph: Arc<Graph>,
52
53    /// Prefix map used for CURIE resolution and qualification.
54    pm: PrefixMap,
55
56    /// Optional base IRI for resolving relative IRIs.
57    base: Option<IriS>,
58
59    /// Counter used to generate unique blank node identifiers.
60    bnode_counter: usize,
61
62    /// Optional Oxigraph store used for SPARQL evaluation.
63    #[cfg(feature = "sparql")]
64    store: Option<Store>,
65}
66
67impl Debug for InMemoryGraph {
68    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
69        f.debug_struct("InMemoryGraph")
70            .field("triples_count", &self.graph.len())
71            .field("prefixmap", &self.pm)
72            .field("base", &self.base)
73            .finish()
74    }
75}
76
77impl Serialize for InMemoryGraph {
78    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
79    where
80        S: serde::Serializer,
81    {
82        let mut state = serializer.serialize_struct("SRDFGraph", 3)?;
83        state.serialize_field("triples_count", &self.graph.len())?;
84        state.serialize_field("prefixmap", &self.pm)?;
85        state.serialize_field("base", &self.base)?;
86        state.end()
87    }
88}
89
90impl InMemoryGraph {
91    /// Creates an empty graph.
92    pub fn new() -> Self {
93        Self::default()
94    }
95
96    /// Returns the number of triples in the graph.
97    pub fn len(&self) -> usize {
98        self.graph.len()
99    }
100
101    /// Returns an iterator over all triples as quads in the default graph.
102    pub fn quads(&self) -> impl Iterator<Item = Quad> + '_ {
103        let graph_name = GraphName::DefaultGraph;
104        self.graph.iter().map(move |t| triple_to_quad(t, graph_name.clone()))
105    }
106
107    /// Returns `true` if the graph contains no triples.
108    pub fn is_empty(&self) -> bool {
109        self.graph.is_empty()
110    }
111
112    /// Merges RDF data from a reader into the current graph.
113    ///
114    /// The parsing behavior depends on [`RDFFormat`] and [`ReaderMode`].
115    /// Prefixes and base IRI are merged when available.
116    ///
117    /// # Parameters
118    ///
119    /// * `reader` - Input stream containing RDF data
120    /// * `source_name` - Name used for error reporting
121    /// * `format` - RDF serialization format
122    /// * `base` - Optional base IRI for resolving relative IRIs
123    /// * `reader_mode` - Controls error handling (strict or lax)
124    ///
125    /// # Errors
126    ///
127    /// Returns an error if parsing fails in strict mode or if I/O errors occur.
128    pub fn merge_from_reader<R: io::Read>(
129        &mut self,
130        reader: &mut R,
131        source_name: &str,
132        format: &RDFFormat,
133        base: Option<&str>,
134        reader_mode: &ReaderMode,
135    ) -> Result<(), InMemoryGraphError> {
136        match format {
137            RDFFormat::Turtle => {
138                self.parse_turtle(reader, source_name, base, reader_mode)?;
139            },
140            RDFFormat::NTriples => {
141                self.parse_ntriples(reader, reader_mode)?;
142            },
143            RDFFormat::Rdfxml => {
144                self.parse_rdfxml(reader, reader_mode)?;
145            },
146            RDFFormat::TriG => {
147                todo!();
148            },
149            RDFFormat::N3 => {
150                todo!();
151            },
152            RDFFormat::NQuads => {
153                self.parse_nquads(reader, reader_mode)?;
154            },
155            RDFFormat::JsonLd => {
156                self.parse_jsonld(reader, reader_mode)?;
157            },
158        }
159        Ok(())
160    }
161
162    /// Parses Turtle data and merges it into the graph.
163    ///
164    /// # Parameters
165    ///
166    /// * `reader` - Input stream containing Turtle data
167    /// * `source_name` - Name used for error reporting
168    /// * `base` - Optional base IRI for resolving relative IRIs
169    /// * `reader_mode` - Controls error handling (strict or lax)
170    ///
171    /// # Errors
172    ///
173    /// Returns an error if parsing fails in strict mode.
174    fn parse_turtle<R: io::Read>(
175        &mut self,
176        reader: &mut R,
177        source_name: &str,
178        base: Option<&str>,
179        reader_mode: &ReaderMode,
180    ) -> Result<(), InMemoryGraphError> {
181        let turtle_parser = match base {
182            None => TurtleParser::new(),
183            Some(iri) => TurtleParser::new().with_base_iri(iri)?,
184        };
185
186        let mut buffer = Vec::new();
187        reader.read_to_end(&mut buffer)?;
188        let reader1 = Cursor::new(&buffer);
189        let mut turtle_reader = turtle_parser.for_reader(reader1);
190
191        for triple_result in turtle_reader.by_ref() {
192            let triple =
193                match handle_parse_error(triple_result, reader_mode, |e| InMemoryGraphError::TurtleParseError {
194                    source_name: source_name.to_string(),
195                    error: e,
196                })? {
197                    Some(t) => t,
198                    None => continue,
199                };
200            Arc::make_mut(&mut self.graph).insert(triple.as_ref());
201        }
202
203        let prefixes: HashMap<&str, &str> = turtle_reader.prefixes().collect();
204        self.base = match (&self.base, base) {
205            (None, None) => None,
206            (Some(b), None) => Some(b.clone()),
207            (_, Some(b)) => Some(IriS::new_unchecked(b)),
208        };
209        let pm = PrefixMap::from_hashmap(prefixes)?;
210        self.merge_prefixes(pm)?;
211
212        Ok(())
213    }
214
215    /// Parses N-Triples data and merges it into the graph.
216    ///
217    /// # Parameters
218    ///
219    /// * `reader` - Input stream containing N-Triples data
220    /// * `reader_mode` - Controls error handling (strict or lax)
221    ///
222    /// # Errors
223    ///
224    /// Returns an error if parsing fails in strict mode.
225    fn parse_ntriples<R: io::Read>(
226        &mut self,
227        reader: &mut R,
228        reader_mode: &ReaderMode,
229    ) -> Result<(), InMemoryGraphError> {
230        let parser = NTriplesParser::new();
231        let mut nt_reader = parser.for_reader(reader);
232
233        for triple_result in nt_reader.by_ref() {
234            let triple = match handle_parse_error(triple_result, reader_mode, |e| InMemoryGraphError::NTriplesError {
235                data: "Reading N-Triples".to_string(),
236                error: e,
237            })? {
238                Some(t) => t,
239                None => continue,
240            };
241            Arc::make_mut(&mut self.graph).insert(triple.as_ref());
242        }
243
244        Ok(())
245    }
246
247    /// Parses RDF/XML data and merges it into the graph.
248    ///
249    /// # Parameters
250    ///
251    /// * `reader` - Input stream containing RDF/XML data
252    /// * `reader_mode` - Controls error handling (strict or lax)
253    ///
254    /// # Errors
255    ///
256    /// Returns an error if parsing fails in strict mode.
257    fn parse_rdfxml<R: io::Read>(
258        &mut self,
259        reader: &mut R,
260        reader_mode: &ReaderMode,
261    ) -> Result<(), InMemoryGraphError> {
262        let parser = RdfXmlParser::new();
263        let mut xml_reader = parser.for_reader(reader);
264
265        for triple_result in xml_reader.by_ref() {
266            let triple = match handle_parse_error(triple_result, reader_mode, |e| InMemoryGraphError::RDFXMLError {
267                data: "Reading RDF/XML".to_string(),
268                error: e,
269            })? {
270                Some(t) => t,
271                None => continue,
272            };
273            let triple_ref = cnv_triple(&triple);
274            Arc::make_mut(&mut self.graph).insert(triple_ref);
275        }
276
277        Ok(())
278    }
279
280    /// Parses N-Quads data and merges it into the graph.
281    ///
282    /// # Parameters
283    ///
284    /// * `reader` - Input stream containing N-Quads data
285    /// * `reader_mode` - Controls error handling (strict or lax)
286    ///
287    /// # Errors
288    ///
289    /// Returns an error if parsing fails in strict mode.
290    fn parse_nquads<R: io::Read>(
291        &mut self,
292        reader: &mut R,
293        reader_mode: &ReaderMode,
294    ) -> Result<(), InMemoryGraphError> {
295        let parser = NQuadsParser::new();
296        let mut nq_reader = parser.for_reader(reader);
297
298        for triple_result in nq_reader.by_ref() {
299            let triple = match handle_parse_error(triple_result, reader_mode, |e| InMemoryGraphError::NQuadsError {
300                data: "Reading NQuads".to_string(),
301                error: e,
302            })? {
303                Some(t) => t,
304                None => continue,
305            };
306            Arc::make_mut(&mut self.graph).insert(triple.as_ref());
307        }
308
309        Ok(())
310    }
311
312    /// Parses JSON-LD data and merges it into the graph.
313    ///
314    /// # Parameters
315    ///
316    /// * `reader` - Input stream containing JSON-LD data
317    /// * `reader_mode` - Controls error handling (strict or lax)
318    ///
319    /// # Errors
320    ///
321    /// Returns an error if parsing fails in strict mode.
322    fn parse_jsonld<R: io::Read>(
323        &mut self,
324        reader: &mut R,
325        reader_mode: &ReaderMode,
326    ) -> Result<(), InMemoryGraphError> {
327        let parser = JsonLdParser::new();
328        let mut jsonld_reader = parser.for_reader(reader);
329
330        for triple_result in jsonld_reader.by_ref() {
331            let triple = match handle_parse_error(triple_result, reader_mode, |e| InMemoryGraphError::JsonLDError {
332                data: "Reading JSON-LD".to_string(),
333                error: e,
334            })? {
335                Some(t) => t,
336                None => continue,
337            };
338            Arc::make_mut(&mut self.graph).insert(triple.as_ref());
339        }
340
341        Ok(())
342    }
343
344    /// Merges a prefix map into the graph's prefix map.
345    ///
346    /// # Parameters
347    ///
348    /// * `prefixmap` - The prefix map to merge
349    ///
350    /// # Errors
351    ///
352    /// Returns an error if merging fails due to conflicting prefixes.
353    pub fn merge_prefixes(&mut self, prefixmap: PrefixMap) -> Result<(), InMemoryGraphError> {
354        self.pm.merge(prefixmap)?;
355        Ok(())
356    }
357
358    /// Builds a new graph from a reader.
359    ///
360    /// This is a convenience constructor that creates an empty graph and merges
361    /// data from the reader.
362    ///
363    /// # Parameters
364    ///
365    /// * `read` - Input stream containing RDF data
366    /// * `source_name` - Name used for error reporting
367    /// * `format` - RDF serialization format
368    /// * `base` - Optional base IRI for resolving relative IRIs
369    /// * `reader_mode` - Controls error handling (strict or lax)
370    ///
371    /// # Errors
372    ///
373    /// Returns an error if parsing fails.
374    pub fn from_reader<R: io::Read>(
375        read: &mut R,
376        source_name: &str,
377        format: &RDFFormat,
378        base: Option<&str>,
379        reader_mode: &ReaderMode,
380    ) -> Result<InMemoryGraph, InMemoryGraphError> {
381        let mut srdf_graph = InMemoryGraph::new();
382        srdf_graph.merge_from_reader(read, source_name, format, base, reader_mode)?;
383        Ok(srdf_graph)
384    }
385
386    /// Resolves a CURIE or prefixed name into a full IRI.
387    ///
388    /// Uses the graph's prefix map to expand the prefixed form.
389    ///
390    /// # Parameters
391    ///
392    /// * `str` - The CURIE or prefixed name to resolve (e.g., "ex:Alice")
393    ///
394    /// # Errors
395    ///
396    /// Returns an error if the prefix is not defined or if the string format is invalid.
397    pub fn resolve(&self, str: &str) -> Result<OxNamedNode, InMemoryGraphError> {
398        let r = self.pm.resolve(str)?;
399        Ok(Self::cnv_iri(r))
400    }
401
402    /// Formats a blank node for display with color.
403    ///
404    /// Returns a green-colored string representation.
405    ///
406    /// # Parameters
407    ///
408    /// * `bn` - The blank node to format
409    pub fn show_blanknode(&self, bn: &OxBlankNode) -> String {
410        format!("{}", bn.to_string().green())
411    }
412
413    /// Formats a literal for display with color.
414    ///
415    /// Returns a red-colored string representation.
416    ///
417    /// # Parameters
418    ///
419    /// * `lit` - The literal to format
420    pub fn show_literal(&self, lit: &OxLiteral) -> String {
421        format!("{}", lit.to_string().red())
422    }
423
424    /// Builds a graph from a string.
425    ///
426    /// # Parameters
427    ///
428    /// * `data` - RDF data as a string
429    /// * `format` - RDF serialization format
430    /// * `base` - Optional base IRI for resolving relative IRIs
431    /// * `reader_mode` - Controls error handling (strict or lax)
432    ///
433    /// # Errors
434    ///
435    /// Returns an error if parsing fails.
436    pub fn from_str(
437        data: &str,
438        format: &RDFFormat,
439        base: Option<&str>,
440        reader_mode: &ReaderMode,
441    ) -> Result<InMemoryGraph, InMemoryGraphError> {
442        Self::from_reader(&mut Cursor::new(data), "String", format, base, reader_mode)
443    }
444
445    /// Converts an [`IriS`] into an Oxigraph named node.
446    ///
447    /// # Parameters
448    ///
449    /// * `iri` - The IRI to convert
450    ///
451    /// # Returns
452    ///
453    /// An Oxigraph named node representation of the IRI.
454    fn cnv_iri(iri: IriS) -> OxNamedNode {
455        OxNamedNode::new_unchecked(iri.as_str())
456    }
457
458    /// Adds a triple using borrowed references.
459    ///
460    /// This method avoids cloning the triple components by working with references.
461    ///
462    /// # Parameters
463    ///
464    /// * `subj` - Triple subject (named node or blank node)
465    /// * `pred` - Triple predicate (named node)
466    /// * `obj` - Triple object (term)
467    ///
468    /// # Errors
469    ///
470    /// This method currently always returns `Ok(())`.
471    pub fn add_triple_ref<'a, S, P, O>(&mut self, subj: S, pred: P, obj: O) -> Result<(), InMemoryGraphError>
472    where
473        S: Into<OxSubjectRef<'a>>,
474        P: Into<NamedNodeRef<'a>>,
475        O: Into<TermRef<'a>>,
476    {
477        let triple = TripleRef::new(subj.into(), pred.into(), obj.into());
478        Arc::make_mut(&mut self.graph).insert(triple);
479        Ok(())
480    }
481}
482
483#[cfg(not(target_family = "wasm"))]
484impl InMemoryGraph {
485    /// Merges RDF data from a filesystem path.
486    ///
487    /// Opens the file and delegates to [`merge_from_reader`](Self::merge_from_reader).
488    ///
489    /// # Parameters
490    ///
491    /// * `path` - Path to the RDF file
492    /// * `format` - RDF serialization format
493    /// * `base` - Optional base IRI for resolving relative IRIs
494    /// * `reader_mode` - Controls error handling (strict or lax)
495    ///
496    /// # Errors
497    ///
498    /// Returns an error if the file cannot be opened or if parsing fails.
499    pub fn merge_from_path<P: AsRef<Path>>(
500        &mut self,
501        path: P,
502        format: &RDFFormat,
503        base: Option<&str>,
504        reader_mode: &ReaderMode,
505    ) -> Result<(), InMemoryGraphError> {
506        let path_ref = path.as_ref();
507        let file = File::open(path_ref).map_err(|e| InMemoryGraphError::ReadingPathError {
508            path_name: path_ref.display().to_string(),
509            error: e,
510        })?;
511        let mut reader = BufReader::new(file);
512        self.merge_from_reader(&mut reader, &path_ref.display().to_string(), format, base, reader_mode)
513    }
514
515    /// Builds a graph from a filesystem path.
516    ///
517    /// Creates a new empty graph and merges data from the file.
518    ///
519    /// # Parameters
520    ///
521    /// * `path` - Path to the RDF file
522    /// * `format` - RDF serialization format
523    /// * `base` - Optional base IRI for resolving relative IRIs
524    /// * `reader_mode` - Controls error handling (strict or lax)
525    ///
526    /// # Errors
527    ///
528    /// Returns an error if the file cannot be opened or if parsing fails.
529    pub fn from_path<P: AsRef<Path>>(
530        path: P,
531        format: &RDFFormat,
532        base: Option<&str>,
533        reader_mode: &ReaderMode,
534    ) -> Result<InMemoryGraph, InMemoryGraphError> {
535        let path_ref = path.as_ref();
536        let file = File::open(path_ref).map_err(|e| InMemoryGraphError::ReadingPathError {
537            path_name: path_ref.display().to_string(),
538            error: e,
539        })?;
540        let mut reader = BufReader::new(file);
541        Self::from_reader(&mut reader, &path_ref.display().to_string(), format, base, reader_mode)
542    }
543
544    /// Parses data from a relative path within a folder.
545    ///
546    /// Convenience method that joins the data file name with the folder path.
547    ///
548    /// # Parameters
549    ///
550    /// * `data` - Relative file name within the folder
551    /// * `format` - RDF serialization format
552    /// * `folder` - Base directory path
553    /// * `base` - Optional base IRI for resolving relative IRIs
554    /// * `reader_mode` - Controls error handling (strict or lax)
555    ///
556    /// # Errors
557    ///
558    /// Returns an error if the file cannot be opened or if parsing fails.
559    pub fn parse_data(
560        data: &str,
561        format: &RDFFormat,
562        folder: &Path,
563        base: Option<&str>,
564        reader_mode: &ReaderMode,
565    ) -> Result<InMemoryGraph, InMemoryGraphError> {
566        let data_path = folder.join(data);
567        Self::from_path(&data_path, format, base, reader_mode)
568    }
569}
570
571impl InMemoryGraph {
572    /// Returns a reference to the prefix map.
573    pub fn prefixmap(&self) -> &PrefixMap {
574        &self.pm
575    }
576}
577
578/// Implementation of the core `Rdf` trait.
579///
580/// This implementation provides the fundamental RDF operations including type definitions,
581/// prefix resolution, and term qualification (converting full IRIs to prefixed names).
582impl Rdf for InMemoryGraph {
583    type IRI = OxNamedNode;
584    type BNode = OxBlankNode;
585    type Literal = OxLiteral;
586    type Subject = OxSubject;
587    type Term = OxTerm;
588    type Triple = OxTriple;
589    type Err = InMemoryGraphError;
590
591    /// Resolves a prefix and local name to a full IRI.
592    ///
593    /// # Parameters
594    ///
595    /// * `prefix` - The namespace prefix (e.g., "foaf")
596    /// * `local` - The local name (e.g., "Person")
597    ///
598    /// # Returns
599    ///
600    /// The full IRI (e.g., "http://xmlns.com/foaf/0.1/Person")
601    ///
602    /// # Errors
603    ///
604    /// Returns an error if the prefix is not defined in the prefix map.
605    fn resolve_prefix_local(&self, prefix: &str, local: &str) -> Result<IriS, PrefixMapError> {
606        let iri = self.pm.resolve_prefix_local(prefix, local)?;
607        Ok(iri)
608    }
609
610    /// Converts a full IRI to a qualified (prefixed) name if possible.
611    ///
612    /// If the IRI matches a known namespace prefix, it returns a shortened form
613    /// (e.g., "foaf:Person"). Otherwise, it returns the full IRI.
614    ///
615    /// # Parameters
616    ///
617    /// * `node` - The named node to qualify
618    ///
619    /// # Returns
620    ///
621    /// A string representation, either prefixed or full IRI.
622    fn qualify_iri(&self, node: &Self::IRI) -> String {
623        let iri = IriS::from_str(node.as_str()).expect("OxNamedNode should always contain valid IRI");
624        self.pm.qualify(&iri)
625    }
626
627    /// Converts a subject (named node or blank node) to a qualified string.
628    ///
629    /// Named nodes are qualified using the prefix map. Blank nodes are formatted
630    /// with color for display.
631    ///
632    /// # Parameters
633    ///
634    /// * `subj` - The subject to qualify
635    ///
636    /// # Returns
637    ///
638    /// A string representation of the subject.
639    fn qualify_subject(&self, subj: &OxSubject) -> String {
640        match subj {
641            OxSubject::BlankNode(bn) => self.show_blanknode(bn),
642            OxSubject::NamedNode(n) => self.qualify_iri(n),
643        }
644    }
645
646    /// Converts an RDF term (named node, blank node, or literal) to a qualified string.
647    ///
648    /// Different term types are formatted differently:
649    /// - Named nodes: qualified using prefix map
650    /// - Blank nodes: formatted with green color
651    /// - Literals: formatted with red color
652    /// - RDF-star triples: not yet supported
653    ///
654    /// # Parameters
655    ///
656    /// * `term` - The term to qualify
657    ///
658    /// # Returns
659    ///
660    /// A string representation of the term.
661    fn qualify_term(&self, term: &OxTerm) -> String {
662        match term {
663            OxTerm::BlankNode(bn) => self.show_blanknode(bn),
664            OxTerm::Literal(lit) => self.show_literal(lit),
665            OxTerm::NamedNode(n) => self.qualify_iri(n),
666            OxTerm::Triple(_) => unimplemented!("RDF-star triples not yet supported"),
667        }
668    }
669
670    /// Returns a reference to the graph's prefix map.
671    ///
672    /// # Returns
673    ///
674    /// `Some(&PrefixMap)` containing all defined namespace prefixes.
675    fn prefixmap(&self) -> Option<PrefixMap> {
676        Some(self.pm.clone())
677    }
678}
679
680/// Implementation of the `NeighsRDF` trait for navigating graph neighbors.
681///
682/// This implementation provides methods to iterate over triples in the graph,
683/// optionally filtered by subject, predicate, or object patterns.
684impl NeighsRDF for InMemoryGraph {
685    /// Returns an iterator over all triples in the graph.
686    ///
687    /// # Returns
688    ///
689    /// An iterator that yields owned triples.
690    fn triples(&self) -> Result<impl Iterator<Item = Self::Triple>, Self::Err> {
691        Ok(self.graph.iter().map(TripleRef::into_owned))
692    }
693
694    /// Returns an iterator over triples matching a pattern.
695    ///
696    /// Uses the appropriate oxrdf index based on which positions are concrete
697    /// vs wildcard, giving O(k) complexity instead of a full O(n) table scan.
698    ///
699    /// # Parameters
700    ///
701    /// * `subject` - Pattern matcher for the subject
702    /// * `predicate` - Pattern matcher for the predicate
703    /// * `object` - Pattern matcher for the object
704    ///
705    /// # Returns
706    ///
707    /// An iterator that yields triples matching all three patterns.
708    fn triples_matching<S, P, O>(
709        &self,
710        subject: &S,
711        predicate: &P,
712        object: &O,
713    ) -> Result<impl Iterator<Item = Self::Triple> + '_, Self::Err>
714    where
715        S: Matcher<Self::Subject>,
716        P: Matcher<Self::IRI>,
717        O: Matcher<Self::Term>,
718    {
719        // Dispatch to the tightest available index to avoid full scans.
720        let result: Box<dyn Iterator<Item = OxTriple> + '_> = match (subject.value(), predicate.value(), object.value())
721        {
722            (Some(s), Some(p), Some(o)) => {
723                let found = self.graph.contains(TripleRef::new(
724                    OxSubjectRef::from(s),
725                    NamedNodeRef::from(p),
726                    TermRef::from(o),
727                ));
728                let s = s.clone();
729                let p = p.clone();
730                let o = o.clone();
731                Box::new(found.then(|| OxTriple::new(s, p, o)).into_iter())
732            },
733            (Some(s), Some(p), None) => {
734                let s_owned = s.clone();
735                let p_owned = p.clone();
736                Box::new(
737                    self.graph
738                        .objects_for_subject_predicate(OxSubjectRef::from(s), NamedNodeRef::from(p))
739                        .map(move |o| OxTriple::new(s_owned.clone(), p_owned.clone(), o.into_owned())),
740                )
741            },
742            (Some(s), None, Some(o)) => {
743                let o_owned = o.clone();
744                Box::new(
745                    self.graph
746                        .triples_for_subject(OxSubjectRef::from(s))
747                        .map(TripleRef::into_owned)
748                        .filter(move |t| t.object == o_owned),
749                )
750            },
751            (Some(s), None, None) => Box::new(
752                self.graph
753                    .triples_for_subject(OxSubjectRef::from(s))
754                    .map(TripleRef::into_owned),
755            ),
756            (None, Some(p), Some(o)) => {
757                let p_owned = p.clone();
758                let o_owned = o.clone();
759                Box::new(
760                    self.graph
761                        .subjects_for_predicate_object(NamedNodeRef::from(p), TermRef::from(o))
762                        .map(move |s| OxTriple::new(s.into_owned(), p_owned.clone(), o_owned.clone())),
763                )
764            },
765            (None, Some(p), None) => Box::new(
766                self.graph
767                    .triples_for_predicate(NamedNodeRef::from(p))
768                    .map(TripleRef::into_owned),
769            ),
770            (None, None, Some(o)) => Box::new(
771                self.graph
772                    .triples_for_object(TermRef::from(o))
773                    .map(TripleRef::into_owned),
774            ),
775            (None, None, None) => Box::new(self.graph.iter().map(TripleRef::into_owned)),
776        };
777
778        Ok(result)
779    }
780}
781
782#[async_trait]
783impl AsyncRDF for InMemoryGraph {
784    type IRI = OxNamedNode;
785    type BNode = OxBlankNode;
786    type Literal = OxLiteral;
787    type Subject = OxSubject;
788    type Term = OxTerm;
789    type Err = InMemoryGraphError;
790
791    /// Returns all predicates associated with a given subject.
792    ///
793    /// # Parameters
794    ///
795    /// * `subject` - The subject node to query
796    ///
797    /// # Errors
798    ///
799    /// This method currently always returns `Ok`.
800    async fn get_predicates_subject(&self, subject: &OxSubject) -> Result<HashSet<OxNamedNode>, InMemoryGraphError> {
801        let mut results = HashSet::new();
802        for triple in self.graph.triples_for_subject(subject) {
803            let predicate: OxNamedNode = triple.predicate.to_owned().into();
804            results.insert(predicate);
805        }
806        Ok(results)
807    }
808
809    /// Returns all objects for a given subject-predicate pair.
810    ///
811    /// # Parameters
812    ///
813    /// * `subject` - The subject node
814    /// * `pred` - The predicate node
815    ///
816    /// # Errors
817    ///
818    /// This method currently always returns `Ok`.
819    async fn get_objects_for_subject_predicate(
820        &self,
821        subject: &OxSubject,
822        pred: &OxNamedNode,
823    ) -> Result<HashSet<OxTerm>, InMemoryGraphError> {
824        let mut results = HashSet::new();
825        for triple in self.graph.triples_for_subject(subject) {
826            let predicate: OxNamedNode = triple.predicate.to_owned().into();
827            if predicate.eq(pred) {
828                let object: OxTerm = triple.object.to_owned().into();
829                results.insert(object);
830            }
831        }
832        Ok(results)
833    }
834
835    /// Returns all subjects for a given object-predicate pair.
836    ///
837    /// # Parameters
838    ///
839    /// * `object` - The object term
840    /// * `pred` - The predicate node
841    ///
842    /// # Errors
843    ///
844    /// This method currently always returns `Ok`.
845    async fn get_subjects_for_object_predicate(
846        &self,
847        object: &OxTerm,
848        pred: &OxNamedNode,
849    ) -> Result<HashSet<OxSubject>, InMemoryGraphError> {
850        let mut results = HashSet::new();
851        for triple in self.graph.triples_for_object(object) {
852            let predicate: OxNamedNode = triple.predicate.to_owned().into();
853            if predicate.eq(pred) {
854                let subject: OxSubject = triple.subject.to_owned().into();
855                results.insert(subject);
856            }
857        }
858        Ok(results)
859    }
860}
861
862/// Implementation of the `FocusRDF` trait for managing a focus term.
863///
864/// The focus term is used to track a specific RDF term of interest during graph operations.
865/// This can be useful for navigation, querying, or maintaining context during traversals.
866impl FocusRDF for InMemoryGraph {
867    /// Sets the focus term for this graph.
868    ///
869    /// # Parameters
870    ///
871    /// * `focus` - The term to set as the current focus
872    fn set_focus(&mut self, focus: &Self::Term) {
873        self.focus = Some(focus.clone());
874    }
875
876    /// Returns the current focus term, if one is set.
877    ///
878    /// # Returns
879    ///
880    /// * `Some(&Term)` - If a focus term has been set
881    /// * `None` - If no focus term is currently set
882    fn get_focus(&self) -> Option<&Self::Term> {
883        self.focus.as_ref()
884    }
885}
886
887/// Implementation of the `BuildRDF` trait for constructing and modifying RDF graphs.
888///
889/// This implementation provides methods to build RDF graphs programmatically by adding
890/// prefixes, base IRIs, blank nodes, triples, and types. It also supports serialization
891/// to various RDF formats.
892impl BuildRDF for InMemoryGraph {
893    /// Sets the base IRI for the graph.
894    ///
895    /// The base IRI is used to resolve relative IRIs during parsing and serialization.
896    ///
897    /// # Parameters
898    ///
899    /// * `base` - Optional base IRI to set
900    fn add_base(&mut self, base: &Option<IriS>) -> Result<(), Self::Err> {
901        self.base = base.clone();
902        Ok(())
903    }
904
905    /// Adds a prefix mapping to the graph's prefix map.
906    ///
907    /// Prefix mappings are used to abbreviate IRIs in qualified form (CURIEs).
908    ///
909    /// # Parameters
910    ///
911    /// * `alias` - The prefix alias (e.g., "ex", "foaf")
912    /// * `iri` - The full IRI that the alias represents
913    ///
914    /// # Errors
915    ///
916    /// Returns an error if the prefix cannot be added to the prefix map.
917    fn add_prefix(&mut self, alias: &str, iri: &IriS) -> Result<(), Self::Err> {
918        self.pm.add_prefix(alias, iri.clone())?;
919        Ok(())
920    }
921
922    /// Replaces the entire prefix map with a new one.
923    ///
924    /// # Parameters
925    ///
926    /// * `prefix_map` - The new prefix map to use
927    fn add_prefix_map(&mut self, prefix_map: PrefixMap) -> Result<(), Self::Err> {
928        self.pm = prefix_map;
929        Ok(())
930    }
931
932    /// Generates a new unique blank node.
933    ///
934    /// Each call to this method increments an internal counter to ensure uniqueness.
935    ///
936    /// # Errors
937    ///
938    /// Returns an error if the blank node counter overflows (extremely unlikely).
939    fn add_bnode(&mut self) -> Result<Self::BNode, Self::Err> {
940        self.bnode_counter += 1;
941        let bn = u128::try_from(self.bnode_counter).map_err(|_| InMemoryGraphError::BlankNodeId {
942            msg: format!("Blank node counter overflow: {}", self.bnode_counter),
943        })?;
944        Ok(OxBlankNode::new_from_unique_id(bn))
945    }
946
947    /// Adds a triple to the graph.
948    ///
949    /// # Parameters
950    ///
951    /// * `subj` - The subject of the triple
952    /// * `pred` - The predicate of the triple
953    /// * `obj` - The object of the triple
954    fn add_triple<S, P, O>(&mut self, subj: S, pred: P, obj: O) -> Result<(), Self::Err>
955    where
956        S: Into<Self::Subject>,
957        P: Into<Self::IRI>,
958        O: Into<Self::Term>,
959    {
960        let triple = OxTriple::new(subj.into(), pred.into(), obj.into());
961        Arc::make_mut(&mut self.graph).insert(&triple);
962        Ok(())
963    }
964
965    /// Removes a triple from the graph.
966    ///
967    /// # Parameters
968    ///
969    /// * `subj` - The subject of the triple to remove
970    /// * `pred` - The predicate of the triple to remove
971    /// * `obj` - The object of the triple to remove
972    fn remove_triple<S, P, O>(&mut self, subj: S, pred: P, obj: O) -> Result<(), Self::Err>
973    where
974        S: Into<Self::Subject>,
975        P: Into<Self::IRI>,
976        O: Into<Self::Term>,
977    {
978        let triple = OxTriple::new(subj.into(), pred.into(), obj.into());
979        Arc::make_mut(&mut self.graph).remove(&triple);
980        Ok(())
981    }
982
983    /// Adds an `rdf:type` assertion to the graph.
984    ///
985    /// This is a convenience method that adds a triple with `rdf:type` as the predicate.
986    ///
987    /// # Parameters
988    ///
989    /// * `node` - The subject that has the type
990    /// * `type_` - The type (class) of the subject
991    fn add_type<S, T>(&mut self, node: S, type_: T) -> Result<(), Self::Err>
992    where
993        S: Into<Self::Subject>,
994        T: Into<Self::Term>,
995    {
996        let triple = OxTriple::new(node.into(), rdf_type(), type_.into());
997        Arc::make_mut(&mut self.graph).insert(&triple);
998        Ok(())
999    }
1000
1001    /// Creates a new empty graph.
1002    ///
1003    /// # Returns
1004    ///
1005    /// A new `InMemoryGraph` with no triples, prefixes, or base IRI.
1006    fn empty() -> Self {
1007        InMemoryGraph {
1008            focus: None,
1009            graph: Graph::new().into(),
1010            pm: PrefixMap::new(),
1011            base: None,
1012            bnode_counter: 0,
1013            #[cfg(feature = "sparql")]
1014            store: None,
1015        }
1016    }
1017
1018    /// Serializes the graph to a writer in the specified RDF format.
1019    ///
1020    /// All prefixes defined in the graph's prefix map are included in the serialization.
1021    ///
1022    /// # Parameters
1023    ///
1024    /// * `format` - The RDF serialization format to use
1025    /// * `write` - The writer to serialize to
1026    ///
1027    /// # Errors
1028    ///
1029    /// Returns an error if serialization fails or if the writer encounters an I/O error.
1030    fn serialize<W: Write>(&self, format: &RDFFormat, write: &mut W) -> Result<(), Self::Err> {
1031        let mut serializer = RdfSerializer::from_format(cnv_rdf_format(format));
1032
1033        for (prefix, iri) in &self.pm.map {
1034            serializer = serializer.with_prefix(prefix, iri.as_str()).unwrap();
1035        }
1036
1037        let mut writer = serializer.for_writer(write);
1038        for triple in self.graph.iter() {
1039            writer.serialize_triple(triple)?;
1040        }
1041        writer.finish()?;
1042        Ok(())
1043    }
1044}
1045
1046/// Converts an RDF format enum to the Oxigraph RdfFormat type.
1047///
1048/// # Parameters
1049///
1050/// * `rdf_format` - The RDF format to convert
1051///
1052/// # Returns
1053///
1054/// The corresponding Oxigraph RdfFormat.
1055fn cnv_rdf_format(rdf_format: &RDFFormat) -> RdfFormat {
1056    match rdf_format {
1057        RDFFormat::NTriples => RdfFormat::NTriples,
1058        RDFFormat::Turtle => RdfFormat::Turtle,
1059        RDFFormat::Rdfxml => RdfFormat::RdfXml,
1060        RDFFormat::TriG => RdfFormat::TriG,
1061        RDFFormat::N3 => RdfFormat::N3,
1062        RDFFormat::NQuads => RdfFormat::NQuads,
1063        RDFFormat::JsonLd => RdfFormat::JsonLd {
1064            profile: JsonLdProfileSet::empty(),
1065        },
1066    }
1067}
1068
1069/// Returns the RDF type predicate IRI.
1070///
1071/// # Returns
1072///
1073/// An Oxigraph named node representing `rdf:type`.
1074fn rdf_type() -> OxNamedNode {
1075    OxNamedNode::new_unchecked(RdfVocab::RDF_TYPE)
1076}
1077
1078/// Converts a triple reference to a quad with the specified graph name.
1079///
1080/// # Parameters
1081///
1082/// * `t` - The triple reference to convert
1083/// * `graph_name` - The graph name to use for the quad
1084///
1085/// # Returns
1086///
1087/// A quad representing the triple in the specified graph.
1088fn triple_to_quad(t: TripleRef, graph_name: GraphName) -> Quad {
1089    let subj: oxrdf::NamedOrBlankNode = t.subject.into();
1090    let pred: oxrdf::NamedNode = t.predicate.into();
1091    let obj: oxrdf::Term = t.object.into();
1092    Quad::new(subj, pred, obj, graph_name)
1093}
1094
1095/// Helper function to handle parse errors consistently.
1096///
1097/// This function implements a consistent error handling strategy across all parsers.
1098/// In strict mode, errors are propagated. In lax mode, errors are logged and parsing continues.
1099///
1100/// # Parameters
1101///
1102/// * `result` - The parse result to handle
1103/// * `reader_mode` - Controls error handling behavior
1104/// * `error_constructor` - Function to construct an appropriate error type
1105///
1106/// # Returns
1107///
1108/// * `Ok(Some(value))` - Parsing succeeded
1109/// * `Ok(None)` - Parsing failed in lax mode (skip this item)
1110/// * `Err(error)` - Parsing failed in strict mode
1111fn handle_parse_error<T, E: std::fmt::Display>(
1112    result: Result<T, E>,
1113    reader_mode: &ReaderMode,
1114    error_constructor: impl FnOnce(String) -> InMemoryGraphError,
1115) -> Result<Option<T>, InMemoryGraphError> {
1116    match result {
1117        Ok(val) => Ok(Some(val)),
1118        Err(e) => {
1119            if reader_mode.is_strict() {
1120                Err(error_constructor(e.to_string()))
1121            } else {
1122                Ok(None)
1123            }
1124        },
1125    }
1126}
1127
1128/// Reader mode when parsing RDF data files.
1129///
1130/// Controls how parsing errors are handled during RDF data ingestion.
1131///
1132/// # Variants
1133///
1134/// * `Strict` - Stops when there is an error
1135/// * `Lax` - Emits a warning and continues processing
1136#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Default, Debug)]
1137pub enum ReaderMode {
1138    /// Stops when there is an error.
1139    #[default]
1140    Strict,
1141
1142    /// Emits a warning and continues processing.
1143    Lax,
1144}
1145
1146impl ReaderMode {
1147    /// Returns `true` if this is strict mode.
1148    pub fn is_strict(&self) -> bool {
1149        matches!(self, ReaderMode::Strict)
1150    }
1151}
1152
1153impl FromStr for ReaderMode {
1154    type Err = String;
1155
1156    fn from_str(s: &str) -> Result<Self, Self::Err> {
1157        match s.to_lowercase().as_str() {
1158            "strict" => Ok(ReaderMode::Strict),
1159            "lax" => Ok(ReaderMode::Lax),
1160            _ => Err(format!("Unknown reader mode format: {s}. Expected 'strict' or 'lax'")),
1161        }
1162    }
1163}
1164
1165impl Display for ReaderMode {
1166    fn fmt(&self, dest: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
1167        match &self {
1168            ReaderMode::Strict => write!(dest, "strict"),
1169            ReaderMode::Lax => write!(dest, "lax"),
1170        }
1171    }
1172}
1173
1174/// Converts an owned triple to a triple reference.
1175///
1176/// # Parameters
1177///
1178/// * `t` - The owned triple to convert
1179///
1180/// # Returns
1181///
1182/// A triple reference with the same subject, predicate, and object.
1183fn cnv_triple(t: &OxTriple) -> TripleRef<'_> {
1184    TripleRef::new(
1185        OxSubjectRef::from(&t.subject),
1186        NamedNodeRef::from(&t.predicate),
1187        TermRef::from(&t.object),
1188    )
1189}
1190
1191#[cfg(feature = "sparql")]
1192impl QueryRDF for InMemoryGraph {
1193    fn query_construct(&self, _query_str: &str, _format: &QueryResultFormat) -> Result<String, InMemoryGraphError>
1194    where
1195        Self: Sized,
1196    {
1197        Ok(String::new())
1198    }
1199
1200    /// Executes a SPARQL SELECT query against the graph.
1201    ///
1202    /// # Parameters
1203    ///
1204    /// * `query_str` - The SPARQL SELECT query string
1205    ///
1206    /// # Errors
1207    ///
1208    /// Returns an error if:
1209    /// * The query cannot be parsed
1210    /// * The query execution fails
1211    /// * The results cannot be processed
1212    ///
1213    /// # Returns
1214    ///
1215    /// Query solutions containing the results of the SELECT query.
1216    fn query_select(&self, query_str: &str) -> Result<QuerySolutions<InMemoryGraph>, InMemoryGraphError>
1217    where
1218        Self: Sized,
1219    {
1220        let mut sols = QuerySolutions::empty();
1221
1222        if let Some(store) = &self.store {
1223            let parsed_query =
1224                SparqlEvaluator::new()
1225                    .parse_query(query_str)
1226                    .map_err(|e| InMemoryGraphError::ParsingQueryError {
1227                        msg: format!("Error parsing query: {}", e),
1228                    })?;
1229
1230            let query_results =
1231                parsed_query
1232                    .on_store(store)
1233                    .execute()
1234                    .map_err(|e| InMemoryGraphError::RunningQueryError {
1235                        query: query_str.to_string(),
1236                        msg: format!("Error executing query: {}", e),
1237                    })?;
1238
1239            let solutions = cnv_query_results(query_results)?;
1240
1241            sols.extend(solutions, self.prefixmap().clone()).map_err(|e| {
1242                InMemoryGraphError::ExtendingQuerySolutionsError {
1243                    query: query_str.to_string(),
1244                    error: e.to_string(),
1245                }
1246            })?;
1247        }
1248
1249        Ok(sols)
1250    }
1251
1252    fn query_ask(&self, _query: &str) -> Result<bool, Self::Err> {
1253        todo!()
1254    }
1255}
1256
1257/// Converts Oxigraph query results to internal query solutions.
1258///
1259/// # Parameters
1260///
1261/// * `query_results` - The query results from Oxigraph
1262///
1263/// # Errors
1264///
1265/// Returns an error if any solution cannot be processed.
1266///
1267/// # Returns
1268///
1269/// A vector of query solutions.
1270#[cfg(feature = "sparql")]
1271fn cnv_query_results(query_results: QueryResults) -> Result<Vec<QuerySolution<InMemoryGraph>>, InMemoryGraphError> {
1272    let QueryResults::Solutions(solutions) = query_results else {
1273        return Ok(Vec::new());
1274    };
1275
1276    #[allow(clippy::unused_enumerate_index)]
1277    solutions
1278        .enumerate()
1279        .map(|(_, solution_result)| {
1280            solution_result
1281                .map(cnv_query_solution)
1282                .map_err(|e| InMemoryGraphError::QueryResultError {
1283                    msg: format!("Error getting query solution: {}", e),
1284                })
1285        })
1286        .collect()
1287}
1288
1289/// Converts a single Oxigraph query solution to internal representation.
1290///
1291/// # Parameters
1292///
1293/// * `qs` - The Oxigraph query solution
1294///
1295/// # Returns
1296///
1297/// An internal query solution with variables and values.
1298#[cfg(feature = "sparql")]
1299fn cnv_query_solution(qs: SparQuerySolution) -> QuerySolution<InMemoryGraph> {
1300    let mut variables = Vec::new();
1301    let mut values = Vec::new();
1302    for v in qs.variables() {
1303        let varname = VarName::new(v.as_str());
1304        variables.push(varname);
1305    }
1306    for t in qs.values() {
1307        let term = t.clone();
1308        values.push(term)
1309    }
1310    QuerySolution::new(variables, values)
1311}