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}