Skip to main content

pyshacl_turbo_engine/
query_graph.rs

1//! SPARQL adapter over a borrowed graph without copying the logical dataset.
2use oxigraph::model::*;
3use spareval::{InternalQuad, QueryableDataset};
4
5use crate::{Error, GraphRead, Result};
6
7/// Evaluate SPARQL over the exact graph contexts exposed by a borrowed source.
8/// Compose union/overlay views explicitly before constructing this adapter.
9/// The adapter adds no default union, snapshots, or graph-name collections.
10/// See the [crate architecture and runnable example](crate#borrowed-sparql-graph-architecture).
11pub struct QueryGraph<'a, G: GraphRead + ?Sized> {
12    source: &'a G,
13}
14
15impl<'a, G: GraphRead + ?Sized> QueryGraph<'a, G> {
16    fn subject(term: &Term) -> Option<NamedOrBlankNodeRef<'_>> {
17        match term {
18            Term::NamedNode(node) => Some(node.as_ref().into()),
19            Term::BlankNode(node) => Some(node.as_ref().into()),
20            _ => None,
21        }
22    }
23
24    /// Borrow a consistent graph source for the lifetime of query evaluation.
25    pub fn new(source: &'a G) -> Self {
26        Self { source }
27    }
28}
29
30impl<'a, G: GraphRead + ?Sized + 'a> QueryableDataset<'a> for QueryGraph<'a, G> {
31    type InternalTerm = Term;
32    type Error = Error;
33
34    fn internal_quads_for_pattern(
35        &self,
36        subject: Option<&Term>,
37        predicate: Option<&Term>,
38        object: Option<&Term>,
39        graph_name: Option<Option<&Term>>,
40    ) -> impl Iterator<Item = Result<InternalQuad<Term>>> + use<'a, G> {
41        let subject = match subject {
42            Some(term) => match Self::subject(term) {
43                Some(value) => Some(value),
44                None => return Box::new(std::iter::empty()) as Box<dyn Iterator<Item = _>>,
45            },
46            None => None,
47        };
48        let predicate = match predicate {
49            Some(Term::NamedNode(node)) => Some(node.as_ref()),
50            Some(_) => return Box::new(std::iter::empty()),
51            None => None,
52        };
53        let graph = match graph_name {
54            Some(Some(term)) => match Self::subject(term) {
55                Some(value) => Some(GraphNameRef::from(value)),
56                None => return Box::new(std::iter::empty()),
57            },
58            Some(None) => Some(GraphNameRef::DefaultGraph),
59            None => None,
60        };
61        // SPARQL's wildcard graph means named graphs only; GraphRead includes
62        // the default graph when its graph filter is absent.
63        let named_only = graph_name.is_none();
64        Box::new(
65            self.source
66                .matching_quads(subject, predicate, object.map(Term::as_ref), graph)
67                .filter(move |quad| {
68                    !named_only
69                        || match quad {
70                            Ok(q) => !q.graph_name.is_default_graph(),
71                            Err(_) => true,
72                        }
73                })
74                .map(|quad| {
75                    quad.map(|quad| InternalQuad {
76                        subject: quad.subject.into(),
77                        predicate: quad.predicate.into(),
78                        object: quad.object,
79                        graph_name: match quad.graph_name {
80                            GraphName::NamedNode(node) => Some(node.into()),
81                            GraphName::BlankNode(node) => Some(node.into()),
82                            GraphName::DefaultGraph => None,
83                        },
84                    })
85                }),
86        )
87    }
88
89    fn internal_named_graphs(&self) -> impl Iterator<Item = Result<Term>> + use<'a, G> {
90        self.source
91            .named_graphs()
92            .map(|graph| graph.map(Into::into))
93    }
94
95    fn contains_internal_graph_name(&self, graph: &Term) -> Result<bool> {
96        match Self::subject(graph) {
97            Some(graph) => self.source.contains_named_graph(&graph.into_owned()),
98            None => Ok(false),
99        }
100    }
101
102    fn internalize_term(&self, term: Term) -> Result<Term> {
103        Ok(term)
104    }
105
106    fn externalize_term(&self, term: Term) -> Result<Term> {
107        Ok(term)
108    }
109}