1use crate::frozen::{FrozenIndexedDataset, GraphSel};
15use oxrdf::{Graph, NamedNode, NamedOrBlankNode, Term};
16use shifty_algebra::Path;
17use std::collections::{BTreeSet, HashSet};
18
19pub trait PathBackend {
23 fn objects(&self, subject: &Term, predicate: &NamedNode) -> HashSet<Term>;
25 fn subjects(&self, predicate: &NamedNode, object: &Term) -> HashSet<Term>;
27 fn out_predicates(&self, subject: &Term) -> BTreeSet<NamedNode>;
29
30 fn contains(&self, subject: &Term, predicate: &NamedNode, object: &Term) -> bool {
37 self.objects(subject, predicate).contains(object)
38 }
39}
40
41pub fn succ<B: PathBackend + ?Sized>(g: &B, node: &Term, path: &Path) -> HashSet<Term> {
43 match path {
44 Path::Id => once(node.clone()),
45 Path::Pred(q) => g.objects(node, q),
46 Path::Inverse(p) => pred(g, node, p),
47 Path::Seq(ps) => fold_seq(g, node, ps, succ),
48 Path::Alt(ps) => ps.iter().flat_map(|p| succ(g, node, p)).collect(),
49 Path::Star(p) => closure(g, node, |g, n| succ(g, n, p)),
50 }
51}
52
53pub fn pred<B: PathBackend + ?Sized>(g: &B, node: &Term, path: &Path) -> HashSet<Term> {
55 match path {
56 Path::Id => once(node.clone()),
57 Path::Pred(q) => g.subjects(q, node),
58 Path::Inverse(p) => succ(g, node, p),
59 Path::Seq(ps) => {
61 let mut cursor = once(node.clone());
62 for p in ps.iter().rev() {
63 cursor = cursor.iter().flat_map(|x| pred(g, x, p)).collect();
64 }
65 cursor
66 }
67 Path::Alt(ps) => ps.iter().flat_map(|p| pred(g, node, p)).collect(),
68 Path::Star(p) => closure(g, node, |g, n| pred(g, n, p)),
69 }
70}
71
72fn fold_seq<B: PathBackend + ?Sized>(
73 g: &B,
74 node: &Term,
75 ps: &[Path],
76 step: fn(&B, &Term, &Path) -> HashSet<Term>,
77) -> HashSet<Term> {
78 let mut cursor = once(node.clone());
79 for p in ps {
80 cursor = cursor.iter().flat_map(|x| step(g, x, p)).collect();
81 }
82 cursor
83}
84
85fn closure<B, F>(g: &B, node: &Term, step: F) -> HashSet<Term>
87where
88 B: PathBackend + ?Sized,
89 F: Fn(&B, &Term) -> HashSet<Term>,
90{
91 let mut result = once(node.clone());
92 let mut frontier = vec![node.clone()];
93 while let Some(x) = frontier.pop() {
94 for y in step(g, &x) {
95 if result.insert(y.clone()) {
96 frontier.push(y);
97 }
98 }
99 }
100 result
101}
102
103fn once(t: Term) -> HashSet<Term> {
104 let mut s = HashSet::with_capacity(1);
105 s.insert(t);
106 s
107}
108
109impl PathBackend for Graph {
114 fn objects(&self, subject: &Term, predicate: &NamedNode) -> HashSet<Term> {
115 match node_of(subject) {
116 Some(s) => self
117 .objects_for_subject_predicate(&s, predicate.as_ref())
118 .map(|t| t.into_owned())
119 .collect(),
120 None => HashSet::new(),
121 }
122 }
123
124 fn subjects(&self, predicate: &NamedNode, object: &Term) -> HashSet<Term> {
125 self.subjects_for_predicate_object(predicate.as_ref(), object)
126 .map(|s| term_of(s.into_owned()))
127 .collect()
128 }
129
130 fn out_predicates(&self, subject: &Term) -> BTreeSet<NamedNode> {
131 let mut preds = BTreeSet::new();
132 if let Some(n) = node_of(subject) {
133 for t in self.triples_for_subject(&n) {
134 preds.insert(t.predicate.into_owned());
135 }
136 }
137 preds
138 }
139
140 fn contains(&self, subject: &Term, predicate: &NamedNode, object: &Term) -> bool {
141 node_of(subject).is_some_and(|s| {
142 Graph::contains(
143 self,
144 oxrdf::TripleRef::new(s.as_ref(), predicate.as_ref(), object.as_ref()),
145 )
146 })
147 }
148}
149
150impl PathBackend for FrozenIndexedDataset {
156 fn objects(&self, subject: &Term, predicate: &NamedNode) -> HashSet<Term> {
157 let s = self.intern(subject);
158 let p = self.intern(&Term::NamedNode(predicate.clone()));
159 self.scan(Some(s), Some(p), None, GraphSel::Default)
160 .map(|t| self.externalize_id(t[2]))
161 .collect()
162 }
163
164 fn subjects(&self, predicate: &NamedNode, object: &Term) -> HashSet<Term> {
165 let p = self.intern(&Term::NamedNode(predicate.clone()));
166 let o = self.intern(object);
167 self.scan(None, Some(p), Some(o), GraphSel::Default)
168 .map(|t| self.externalize_id(t[0]))
169 .collect()
170 }
171
172 fn out_predicates(&self, subject: &Term) -> BTreeSet<NamedNode> {
173 let s = self.intern(subject);
174 self.scan(Some(s), None, None, GraphSel::Default)
175 .filter_map(|t| match self.externalize_id(t[1]) {
176 Term::NamedNode(p) => Some(p),
177 _ => None,
178 })
179 .collect()
180 }
181
182 fn contains(&self, subject: &Term, predicate: &NamedNode, object: &Term) -> bool {
183 let s = self.intern(subject);
184 let p = self.intern(&Term::NamedNode(predicate.clone()));
185 let o = self.intern(object);
186 self.scan(Some(s), Some(p), Some(o), GraphSel::Default)
187 .next()
188 .is_some()
189 }
190}
191
192pub fn node_of(term: &Term) -> Option<NamedOrBlankNode> {
194 match term {
195 Term::NamedNode(n) => Some(NamedOrBlankNode::NamedNode(n.clone())),
196 Term::BlankNode(b) => Some(NamedOrBlankNode::BlankNode(b.clone())),
197 Term::Literal(_) => None,
198 }
199}
200
201pub fn term_of(node: NamedOrBlankNode) -> Term {
202 match node {
203 NamedOrBlankNode::NamedNode(n) => Term::NamedNode(n),
204 NamedOrBlankNode::BlankNode(b) => Term::BlankNode(b),
205 }
206}
207
208#[cfg(test)]
209mod tests {
210 use super::*;
211 use oxrdf::{NamedNode, Triple};
212
213 fn nn(iri: &str) -> NamedNode {
214 NamedNode::new(iri).unwrap()
215 }
216
217 fn t(s: &str, p: &str, o: &str) -> Triple {
218 Triple::new(nn(s), nn(p), nn(o))
219 }
220
221 fn term(iri: &str) -> Term {
222 Term::NamedNode(nn(iri))
223 }
224
225 fn sample_graph() -> Graph {
227 let mut g = Graph::new();
228 for tr in [
229 t("http://ex/a", "http://ex/p", "http://ex/b"),
230 t("http://ex/a", "http://ex/p", "http://ex/e"),
231 t("http://ex/b", "http://ex/p", "http://ex/c"),
232 t("http://ex/c", "http://ex/q", "http://ex/d"),
233 ] {
234 g.insert(tr.as_ref());
235 }
236 g
237 }
238
239 fn p(iri: &str) -> Path {
240 Path::Pred(nn(iri))
241 }
242
243 fn sample_paths() -> Vec<Path> {
245 let pp = || p("http://ex/p");
246 let qq = || p("http://ex/q");
247 vec![
248 Path::Id,
249 pp(),
250 Path::Inverse(Box::new(pp())),
251 Path::Seq(vec![pp(), pp()]),
252 Path::Seq(vec![pp(), qq()]),
253 Path::Alt(vec![pp(), qq()]),
254 Path::Star(Box::new(pp())),
255 Path::Seq(vec![Path::Star(Box::new(pp())), qq()]),
256 Path::Inverse(Box::new(Path::Seq(vec![pp(), pp()]))),
257 ]
258 }
259
260 #[test]
261 fn graph_and_frozen_backends_agree() {
262 let g = sample_graph();
263 let frozen = FrozenIndexedDataset::from_graph(&g);
264 let nodes = ["http://ex/a", "http://ex/b", "http://ex/c", "http://ex/d"];
265 for path in sample_paths() {
266 for node in nodes {
267 let n = term(node);
268 assert_eq!(
269 succ(&g, &n, &path),
270 succ(&frozen, &n, &path),
271 "succ mismatch at {node} for {path:?}"
272 );
273 assert_eq!(
274 pred(&g, &n, &path),
275 pred(&frozen, &n, &path),
276 "pred mismatch at {node} for {path:?}"
277 );
278 }
279 }
280 }
281
282 #[test]
283 fn frozen_succ_pred_basics() {
284 let g = sample_graph();
285 let frozen = FrozenIndexedDataset::from_graph(&g);
286 let a = term("http://ex/a");
287 assert_eq!(
288 succ(&frozen, &a, &p("http://ex/p")),
289 HashSet::from([term("http://ex/b"), term("http://ex/e")])
290 );
291 let c = term("http://ex/c");
292 assert_eq!(
293 pred(&frozen, &c, &p("http://ex/p")),
294 HashSet::from([term("http://ex/b")])
295 );
296 }
297
298 #[test]
299 fn star_is_reflexive_transitive_on_both_backends() {
300 let g = sample_graph();
301 let frozen = FrozenIndexedDataset::from_graph(&g);
302 let a = term("http://ex/a");
303 let star = Path::Star(Box::new(p("http://ex/p")));
304 let expected = HashSet::from([
305 term("http://ex/a"),
306 term("http://ex/b"),
307 term("http://ex/e"),
308 term("http://ex/c"),
309 ]);
310 assert_eq!(succ(&g, &a, &star), expected);
311 assert_eq!(succ(&frozen, &a, &star), expected);
312 }
313
314 #[test]
315 fn out_predicates_agree() {
316 let g = sample_graph();
317 let frozen = FrozenIndexedDataset::from_graph(&g);
318 let a = term("http://ex/a");
319 assert_eq!(g.out_predicates(&a), BTreeSet::from([nn("http://ex/p")]));
320 assert_eq!(
321 frozen.out_predicates(&a),
322 BTreeSet::from([nn("http://ex/p")])
323 );
324 let lit = Term::Literal(oxrdf::Literal::new_simple_literal("x"));
326 assert!(g.out_predicates(&lit).is_empty());
327 assert!(frozen.out_predicates(&lit).is_empty());
328 }
329}