1use crate::algebra::{PropertyPath, Term as AlgebraTerm, TriplePattern};
6use crate::path::{PathDataset, PropertyPath as PathPropertyPath};
7use anyhow::{anyhow, Result};
8use oxirs_core::model::{GraphName, NamedNode};
9use oxirs_core::RdfTerm;
10use std::collections::HashSet;
11
12#[derive(Debug, Clone, PartialEq, Eq)]
21pub enum GraphSelector {
22 DefaultGraph,
24 Named(NamedNode),
26}
27
28pub trait Dataset: Send + Sync {
30 fn find_triples(
38 &self,
39 pattern: &TriplePattern,
40 ) -> Result<Vec<(AlgebraTerm, AlgebraTerm, AlgebraTerm)>>;
41
42 fn contains_triple(
44 &self,
45 subject: &AlgebraTerm,
46 predicate: &AlgebraTerm,
47 object: &AlgebraTerm,
48 ) -> Result<bool>;
49
50 fn subjects(&self) -> Result<Vec<AlgebraTerm>>;
52
53 fn predicates(&self) -> Result<Vec<AlgebraTerm>>;
55
56 fn objects(&self) -> Result<Vec<AlgebraTerm>>;
58
59 fn has_graph_support(&self) -> bool {
65 false
66 }
67
68 fn find_triples_in(
76 &self,
77 selector: &GraphSelector,
78 pattern: &TriplePattern,
79 ) -> Result<Vec<(AlgebraTerm, AlgebraTerm, AlgebraTerm)>> {
80 let _ = (selector, pattern);
81 Err(anyhow!(
82 "graph-scoped access not supported by this dataset: it does not \
83 implement Dataset::find_triples_in (has_graph_support() is false)"
84 ))
85 }
86
87 fn named_graphs(&self) -> Result<Vec<AlgebraTerm>> {
93 Err(anyhow!(
94 "named-graph enumeration not supported by this dataset: it does \
95 not implement Dataset::named_graphs (has_graph_support() is false)"
96 ))
97 }
98}
99
100#[derive(Debug, Clone, Default)]
106pub struct InMemoryDataset {
107 quads: Vec<(Option<NamedNode>, AlgebraTerm, AlgebraTerm, AlgebraTerm)>,
108}
109
110impl InMemoryDataset {
111 pub fn new() -> Self {
112 Self { quads: Vec::new() }
113 }
114
115 pub fn add_triple(
117 &mut self,
118 subject: AlgebraTerm,
119 predicate: AlgebraTerm,
120 object: AlgebraTerm,
121 ) {
122 self.quads.push((None, subject, predicate, object));
123 }
124
125 pub fn add_triple_in_graph(
127 &mut self,
128 graph: NamedNode,
129 subject: AlgebraTerm,
130 predicate: AlgebraTerm,
131 object: AlgebraTerm,
132 ) {
133 self.quads.push((Some(graph), subject, predicate, object));
134 }
135
136 pub fn from_triples(triples: Vec<(AlgebraTerm, AlgebraTerm, AlgebraTerm)>) -> Self {
138 Self {
139 quads: triples
140 .into_iter()
141 .map(|(s, p, o)| (None, s, p, o))
142 .collect(),
143 }
144 }
145
146 fn scan(
148 &self,
149 want_graph: Option<&NamedNode>,
150 pattern: &TriplePattern,
151 ) -> Vec<(AlgebraTerm, AlgebraTerm, AlgebraTerm)> {
152 self.quads
153 .iter()
154 .filter(|(g, s, p, o)| {
155 let graph_ok = match (want_graph, g) {
156 (None, None) => true,
157 (Some(want), Some(have)) => want == have,
158 _ => false,
159 };
160 graph_ok
161 && matches_term(&pattern.subject, s)
162 && matches_term(&pattern.predicate, p)
163 && matches_term(&pattern.object, o)
164 })
165 .map(|(_, s, p, o)| (s.clone(), p.clone(), o.clone()))
166 .collect()
167 }
168}
169
170impl Dataset for InMemoryDataset {
171 fn find_triples(
172 &self,
173 pattern: &TriplePattern,
174 ) -> Result<Vec<(AlgebraTerm, AlgebraTerm, AlgebraTerm)>> {
175 Ok(self.scan(None, pattern))
177 }
178
179 fn find_triples_in(
180 &self,
181 selector: &GraphSelector,
182 pattern: &TriplePattern,
183 ) -> Result<Vec<(AlgebraTerm, AlgebraTerm, AlgebraTerm)>> {
184 let want = match selector {
185 GraphSelector::DefaultGraph => None,
186 GraphSelector::Named(iri) => Some(iri),
187 };
188 Ok(self.scan(want, pattern))
189 }
190
191 fn has_graph_support(&self) -> bool {
192 true
193 }
194
195 fn named_graphs(&self) -> Result<Vec<AlgebraTerm>> {
196 let mut seen: HashSet<NamedNode> = HashSet::new();
197 let mut names: Vec<AlgebraTerm> = Vec::new();
198 for (g, _, _, _) in &self.quads {
199 if let Some(iri) = g {
200 if seen.insert(iri.clone()) {
201 names.push(AlgebraTerm::Iri(iri.clone()));
202 }
203 }
204 }
205 Ok(names)
206 }
207
208 fn contains_triple(
209 &self,
210 subject: &AlgebraTerm,
211 predicate: &AlgebraTerm,
212 object: &AlgebraTerm,
213 ) -> Result<bool> {
214 Ok(self
217 .quads
218 .iter()
219 .any(|(g, s, p, o)| g.is_none() && s == subject && p == predicate && o == object))
220 }
221
222 fn subjects(&self) -> Result<Vec<AlgebraTerm>> {
223 let subjects: HashSet<_> = self.quads.iter().map(|(_, s, _, _)| s.clone()).collect();
224 Ok(subjects.into_iter().collect())
225 }
226
227 fn predicates(&self) -> Result<Vec<AlgebraTerm>> {
228 let predicates: HashSet<_> = self.quads.iter().map(|(_, _, p, _)| p.clone()).collect();
229 Ok(predicates.into_iter().collect())
230 }
231
232 fn objects(&self) -> Result<Vec<AlgebraTerm>> {
233 let objects: HashSet<_> = self.quads.iter().map(|(_, _, _, o)| o.clone()).collect();
234 Ok(objects.into_iter().collect())
235 }
236}
237
238fn matches_term(pattern: &AlgebraTerm, term: &AlgebraTerm) -> bool {
239 match pattern {
240 AlgebraTerm::Variable(_) => true, _ => pattern == term,
242 }
243}
244
245pub struct DatasetPathAdapter<'a> {
247 dataset: &'a dyn Dataset,
248}
249
250impl<'a> DatasetPathAdapter<'a> {
251 pub fn new(dataset: &'a dyn Dataset) -> Self {
252 Self { dataset }
253 }
254}
255
256impl<'a> PathDataset for DatasetPathAdapter<'a> {
257 fn find_outgoing(
258 &self,
259 subject: &AlgebraTerm,
260 predicate: &AlgebraTerm,
261 ) -> Result<Vec<AlgebraTerm>> {
262 let pattern = TriplePattern::new(
263 subject.clone(),
264 predicate.clone(),
265 AlgebraTerm::Variable(crate::algebra::Variable::new("?o")?),
266 );
267 let triples = self.dataset.find_triples(&pattern)?;
268 Ok(triples.into_iter().map(|(_, _, o)| o).collect())
269 }
270
271 fn find_incoming(
272 &self,
273 predicate: &AlgebraTerm,
274 object: &AlgebraTerm,
275 ) -> Result<Vec<AlgebraTerm>> {
276 let pattern = TriplePattern::new(
277 AlgebraTerm::Variable(crate::algebra::Variable::new("?s")?),
278 predicate.clone(),
279 object.clone(),
280 );
281 let triples = self.dataset.find_triples(&pattern)?;
282 Ok(triples.into_iter().map(|(s, _, _)| s).collect())
283 }
284
285 fn find_predicates(
286 &self,
287 subject: &AlgebraTerm,
288 object: &AlgebraTerm,
289 ) -> Result<Vec<AlgebraTerm>> {
290 let pattern = TriplePattern::new(
291 subject.clone(),
292 AlgebraTerm::Variable(crate::algebra::Variable::new("?p")?),
293 object.clone(),
294 );
295 let triples = self.dataset.find_triples(&pattern)?;
296 Ok(triples.into_iter().map(|(_, p, _)| p).collect())
297 }
298
299 fn get_predicates(&self) -> Result<Vec<AlgebraTerm>> {
300 self.dataset.predicates()
301 }
302
303 fn contains_triple(
304 &self,
305 subject: &AlgebraTerm,
306 predicate: &AlgebraTerm,
307 object: &AlgebraTerm,
308 ) -> Result<bool> {
309 self.dataset.contains_triple(subject, predicate, object)
310 }
311}
312
313pub fn convert_property_path(path: &PropertyPath) -> Result<PathPropertyPath> {
315 match path {
316 PropertyPath::Iri(iri) => Ok(PathPropertyPath::Direct(AlgebraTerm::Iri(iri.clone()))),
317 PropertyPath::Variable(var) => {
318 Ok(PathPropertyPath::Direct(AlgebraTerm::Variable(var.clone())))
319 }
320 PropertyPath::Inverse(inner) => {
321 let inner_path = convert_property_path(inner)?;
322 Ok(PathPropertyPath::Inverse(Box::new(inner_path)))
323 }
324 PropertyPath::Sequence(left, right) => {
325 let left_path = convert_property_path(left)?;
326 let right_path = convert_property_path(right)?;
327 Ok(PathPropertyPath::Sequence(
328 Box::new(left_path),
329 Box::new(right_path),
330 ))
331 }
332 PropertyPath::Alternative(left, right) => {
333 let left_path = convert_property_path(left)?;
334 let right_path = convert_property_path(right)?;
335 Ok(PathPropertyPath::Alternative(
336 Box::new(left_path),
337 Box::new(right_path),
338 ))
339 }
340 PropertyPath::ZeroOrMore(inner) => {
341 let inner_path = convert_property_path(inner)?;
342 Ok(PathPropertyPath::ZeroOrMore(Box::new(inner_path)))
343 }
344 PropertyPath::OneOrMore(inner) => {
345 let inner_path = convert_property_path(inner)?;
346 Ok(PathPropertyPath::OneOrMore(Box::new(inner_path)))
347 }
348 PropertyPath::ZeroOrOne(inner) => {
349 let inner_path = convert_property_path(inner)?;
350 Ok(PathPropertyPath::ZeroOrOne(Box::new(inner_path)))
351 }
352 PropertyPath::NegatedPropertySet(paths) => {
353 let mut terms = Vec::new();
354 for p in paths {
355 match p {
356 PropertyPath::Iri(iri) => terms.push(AlgebraTerm::Iri(iri.clone())),
357 PropertyPath::Variable(var) => terms.push(AlgebraTerm::Variable(var.clone())),
358 _ => {
359 return Err(anyhow!(
360 "Negated property set can only contain IRIs or variables"
361 ))
362 }
363 }
364 }
365 Ok(PathPropertyPath::NegatedPropertySet(terms))
366 }
367 }
368}
369
370pub struct ConcreteStoreDataset {
373 store: std::sync::Arc<oxirs_core::rdf_store::ConcreteStore>,
374}
375
376impl ConcreteStoreDataset {
377 pub fn new(store: oxirs_core::rdf_store::ConcreteStore) -> Self {
378 Self {
379 store: std::sync::Arc::new(store),
380 }
381 }
382
383 pub fn from_arc(store: std::sync::Arc<oxirs_core::rdf_store::ConcreteStore>) -> Self {
384 Self { store }
385 }
386}
387
388impl Clone for ConcreteStoreDataset {
389 fn clone(&self) -> Self {
390 Self {
391 store: std::sync::Arc::clone(&self.store),
392 }
393 }
394}
395
396impl Dataset for ConcreteStoreDataset {
397 fn find_triples(
398 &self,
399 pattern: &TriplePattern,
400 ) -> Result<Vec<(AlgebraTerm, AlgebraTerm, AlgebraTerm)>> {
401 self.find_triples_in(&GraphSelector::DefaultGraph, pattern)
403 }
404
405 fn find_triples_in(
406 &self,
407 selector: &GraphSelector,
408 pattern: &TriplePattern,
409 ) -> Result<Vec<(AlgebraTerm, AlgebraTerm, AlgebraTerm)>> {
410 use oxirs_core::rdf_store::Store;
411
412 let Some((subject, predicate, object)) = pattern_to_query_terms(pattern)? else {
413 return Ok(Vec::new());
416 };
417 let graph_name = graph_selector_to_name(selector);
418 let quads = self.store.find_quads(
419 subject.as_ref(),
420 predicate.as_ref(),
421 object.as_ref(),
422 Some(&graph_name),
423 )?;
424 Ok(quads_to_algebra_triples(quads))
425 }
426
427 fn has_graph_support(&self) -> bool {
428 true
429 }
430
431 fn named_graphs(&self) -> Result<Vec<AlgebraTerm>> {
432 collect_named_graphs(self.store.as_ref())
433 }
434
435 fn contains_triple(
436 &self,
437 subject: &AlgebraTerm,
438 predicate: &AlgebraTerm,
439 object: &AlgebraTerm,
440 ) -> Result<bool> {
441 let pattern = TriplePattern::new(subject.clone(), predicate.clone(), object.clone());
442 let triples = self.find_triples(&pattern)?;
443 Ok(!triples.is_empty())
444 }
445
446 fn subjects(&self) -> Result<Vec<AlgebraTerm>> {
447 use oxirs_core::rdf_store::Store;
448 let quads = self.store.find_quads(None, None, None, None)?;
449 let subjects: HashSet<_> = quads
450 .into_iter()
451 .filter_map(|quad| match quad.subject() {
452 oxirs_core::model::Subject::NamedNode(n) => Some(AlgebraTerm::Iri(
453 oxirs_core::model::NamedNode::new(n.as_str()).ok()?,
454 )),
455 oxirs_core::model::Subject::BlankNode(b) => {
456 Some(AlgebraTerm::BlankNode(b.as_str().to_string()))
457 }
458 oxirs_core::model::Subject::Variable(v) => Some(AlgebraTerm::Variable(v.clone())),
459 oxirs_core::model::Subject::QuotedTriple(_) => None,
460 })
461 .collect();
462 Ok(subjects.into_iter().collect())
463 }
464
465 fn predicates(&self) -> Result<Vec<AlgebraTerm>> {
466 use oxirs_core::rdf_store::Store;
467 let quads = self.store.find_quads(None, None, None, None)?;
468 let predicates: HashSet<_> = quads
469 .into_iter()
470 .filter_map(|quad| {
471 Some(AlgebraTerm::Iri(
472 oxirs_core::model::NamedNode::new(quad.predicate().as_str()).ok()?,
473 ))
474 })
475 .collect();
476 Ok(predicates.into_iter().collect())
477 }
478
479 fn objects(&self) -> Result<Vec<AlgebraTerm>> {
480 use oxirs_core::rdf_store::Store;
481 let quads = self.store.find_quads(None, None, None, None)?;
482 let objects: HashSet<_> = quads
483 .into_iter()
484 .filter_map(|quad| match quad.object() {
485 oxirs_core::model::Object::NamedNode(n) => Some(AlgebraTerm::Iri(
486 oxirs_core::model::NamedNode::new(n.as_str()).ok()?,
487 )),
488 oxirs_core::model::Object::Literal(l) => {
489 Some(AlgebraTerm::Literal(core_literal_to_algebra(l)))
490 }
491 oxirs_core::model::Object::BlankNode(b) => {
492 Some(AlgebraTerm::BlankNode(b.as_str().to_string()))
493 }
494 oxirs_core::model::Object::Variable(v) => Some(AlgebraTerm::Variable(v.clone())),
495 oxirs_core::model::Object::QuotedTriple(_) => None,
496 })
497 .collect();
498 Ok(objects.into_iter().collect())
499 }
500}
501
502const XSD_STRING: &str = "http://www.w3.org/2001/XMLSchema#string";
505const RDF_LANG_STRING: &str = "http://www.w3.org/1999/02/22-rdf-syntax-ns#langString";
506
507pub(crate) fn algebra_literal_to_core(lit: &crate::algebra::Literal) -> oxirs_core::model::Literal {
512 if let Some(lang) = &lit.language {
513 oxirs_core::model::Literal::new_language_tagged_literal(&lit.value, lang)
514 .unwrap_or_else(|_| oxirs_core::model::Literal::new(&lit.value))
515 } else if let Some(dt) = &lit.datatype {
516 oxirs_core::model::Literal::new_typed(&lit.value, dt.clone())
517 } else {
518 oxirs_core::model::Literal::new(&lit.value)
519 }
520}
521
522pub(crate) fn core_literal_to_algebra(l: &oxirs_core::model::Literal) -> crate::algebra::Literal {
526 if let Some(lang) = l.language() {
527 crate::algebra::Literal {
528 value: l.value().to_string(),
529 language: Some(lang.to_string()),
530 datatype: None,
531 }
532 } else {
533 let dt = l.datatype().into_owned();
534 let datatype = if dt.as_str() == XSD_STRING || dt.as_str() == RDF_LANG_STRING {
535 None
536 } else {
537 Some(dt)
538 };
539 crate::algebra::Literal {
540 value: l.value().to_string(),
541 language: None,
542 datatype,
543 }
544 }
545}
546
547#[allow(clippy::type_complexity)]
559pub(crate) fn pattern_to_query_terms(
560 pattern: &TriplePattern,
561) -> Result<
562 Option<(
563 Option<oxirs_core::model::Subject>,
564 Option<oxirs_core::model::Predicate>,
565 Option<oxirs_core::model::Object>,
566 )>,
567> {
568 let subject = match &pattern.subject {
569 AlgebraTerm::Iri(iri) => Some(oxirs_core::model::Subject::NamedNode(iri.clone())),
570 AlgebraTerm::Variable(_) => None,
571 AlgebraTerm::BlankNode(id) => Some(oxirs_core::model::Subject::BlankNode(
572 oxirs_core::model::BlankNode::new(id)
573 .map_err(|e| anyhow!("Invalid blank node: {}", e))?,
574 )),
575 _ => return Ok(None),
577 };
578
579 let predicate = match &pattern.predicate {
580 AlgebraTerm::Iri(iri) => Some(oxirs_core::model::Predicate::NamedNode(iri.clone())),
581 AlgebraTerm::Variable(_) => None,
582 AlgebraTerm::PropertyPath(path) => match path {
583 PropertyPath::Iri(iri) => Some(oxirs_core::model::Predicate::NamedNode(iri.clone())),
584 PropertyPath::Variable(_) => None,
585 _ => {
588 return Err(anyhow!(
589 "Complex property paths not yet supported in find_triples"
590 ))
591 }
592 },
593 _ => return Ok(None),
595 };
596
597 let object = match &pattern.object {
598 AlgebraTerm::Iri(iri) => Some(oxirs_core::model::Object::NamedNode(iri.clone())),
599 AlgebraTerm::Literal(lit) => Some(oxirs_core::model::Object::Literal(
600 algebra_literal_to_core(lit),
601 )),
602 AlgebraTerm::BlankNode(id) => Some(oxirs_core::model::Object::BlankNode(
603 oxirs_core::model::BlankNode::new(id)
604 .map_err(|e| anyhow!("Invalid blank node: {}", e))?,
605 )),
606 AlgebraTerm::Variable(_) => None,
607 _ => return Ok(None),
609 };
610
611 Ok(Some((subject, predicate, object)))
612}
613
614pub(crate) fn quads_to_algebra_triples(
617 quads: Vec<oxirs_core::model::Quad>,
618) -> Vec<(AlgebraTerm, AlgebraTerm, AlgebraTerm)> {
619 quads
620 .into_iter()
621 .filter_map(|quad| {
622 let s = match quad.subject() {
623 oxirs_core::model::Subject::NamedNode(n) => {
624 AlgebraTerm::Iri(oxirs_core::model::NamedNode::new(n.as_str()).ok()?)
625 }
626 oxirs_core::model::Subject::BlankNode(b) => {
627 AlgebraTerm::BlankNode(b.as_str().to_string())
628 }
629 oxirs_core::model::Subject::Variable(v) => AlgebraTerm::Variable(v.clone()),
630 oxirs_core::model::Subject::QuotedTriple(_) => return None,
631 };
632
633 let p = AlgebraTerm::Iri(
634 oxirs_core::model::NamedNode::new(quad.predicate().as_str()).ok()?,
635 );
636
637 let o = match quad.object() {
638 oxirs_core::model::Object::NamedNode(n) => {
639 AlgebraTerm::Iri(oxirs_core::model::NamedNode::new(n.as_str()).ok()?)
640 }
641 oxirs_core::model::Object::Literal(l) => {
642 AlgebraTerm::Literal(core_literal_to_algebra(l))
643 }
644 oxirs_core::model::Object::BlankNode(b) => {
645 AlgebraTerm::BlankNode(b.as_str().to_string())
646 }
647 oxirs_core::model::Object::Variable(v) => AlgebraTerm::Variable(v.clone()),
648 oxirs_core::model::Object::QuotedTriple(_) => return None,
649 };
650
651 Some((s, p, o))
652 })
653 .collect()
654}
655
656pub(crate) fn graph_selector_to_name(selector: &GraphSelector) -> GraphName {
658 match selector {
659 GraphSelector::DefaultGraph => GraphName::DefaultGraph,
660 GraphSelector::Named(iri) => GraphName::NamedNode(iri.clone()),
661 }
662}
663
664pub(crate) fn collect_named_graphs(
675 store: &dyn oxirs_core::rdf_store::Store,
676) -> Result<Vec<AlgebraTerm>> {
677 Ok(store
678 .named_graphs()?
679 .into_iter()
680 .map(AlgebraTerm::Iri)
681 .collect())
682}
683
684pub struct StoreRefDataset<'s> {
690 store: &'s dyn oxirs_core::rdf_store::Store,
691}
692
693impl<'s> StoreRefDataset<'s> {
694 pub fn new(store: &'s dyn oxirs_core::rdf_store::Store) -> Self {
696 Self { store }
697 }
698}
699
700impl<'s> Dataset for StoreRefDataset<'s> {
701 fn find_triples(
702 &self,
703 pattern: &TriplePattern,
704 ) -> Result<Vec<(AlgebraTerm, AlgebraTerm, AlgebraTerm)>> {
705 self.find_triples_in(&GraphSelector::DefaultGraph, pattern)
709 }
710
711 fn find_triples_in(
712 &self,
713 selector: &GraphSelector,
714 pattern: &TriplePattern,
715 ) -> Result<Vec<(AlgebraTerm, AlgebraTerm, AlgebraTerm)>> {
716 let Some((subject, predicate, object)) = pattern_to_query_terms(pattern)? else {
717 return Ok(Vec::new());
718 };
719 let graph_name = graph_selector_to_name(selector);
720 let quads = self.store.find_quads(
721 subject.as_ref(),
722 predicate.as_ref(),
723 object.as_ref(),
724 Some(&graph_name),
725 )?;
726 Ok(quads_to_algebra_triples(quads))
727 }
728
729 fn has_graph_support(&self) -> bool {
730 true
731 }
732
733 fn named_graphs(&self) -> Result<Vec<AlgebraTerm>> {
734 collect_named_graphs(self.store)
735 }
736
737 fn contains_triple(
738 &self,
739 subject: &AlgebraTerm,
740 predicate: &AlgebraTerm,
741 object: &AlgebraTerm,
742 ) -> Result<bool> {
743 let pattern = TriplePattern::new(subject.clone(), predicate.clone(), object.clone());
744 Ok(!self.find_triples(&pattern)?.is_empty())
745 }
746
747 fn subjects(&self) -> Result<Vec<AlgebraTerm>> {
748 let quads = self.store.find_quads(None, None, None, None)?;
749 let subjects: HashSet<_> = quads
750 .into_iter()
751 .filter_map(|quad| match quad.subject() {
752 oxirs_core::model::Subject::NamedNode(n) => Some(AlgebraTerm::Iri(
753 oxirs_core::model::NamedNode::new(n.as_str()).ok()?,
754 )),
755 oxirs_core::model::Subject::BlankNode(b) => {
756 Some(AlgebraTerm::BlankNode(b.as_str().to_string()))
757 }
758 oxirs_core::model::Subject::Variable(v) => Some(AlgebraTerm::Variable(v.clone())),
759 oxirs_core::model::Subject::QuotedTriple(_) => None,
760 })
761 .collect();
762 Ok(subjects.into_iter().collect())
763 }
764
765 fn predicates(&self) -> Result<Vec<AlgebraTerm>> {
766 let quads = self.store.find_quads(None, None, None, None)?;
767 let predicates: HashSet<_> = quads
768 .into_iter()
769 .filter_map(|quad| {
770 Some(AlgebraTerm::Iri(
771 oxirs_core::model::NamedNode::new(quad.predicate().as_str()).ok()?,
772 ))
773 })
774 .collect();
775 Ok(predicates.into_iter().collect())
776 }
777
778 fn objects(&self) -> Result<Vec<AlgebraTerm>> {
779 let quads = self.store.find_quads(None, None, None, None)?;
780 let objects: HashSet<_> = quads
781 .into_iter()
782 .filter_map(|quad| match quad.object() {
783 oxirs_core::model::Object::NamedNode(n) => Some(AlgebraTerm::Iri(
784 oxirs_core::model::NamedNode::new(n.as_str()).ok()?,
785 )),
786 oxirs_core::model::Object::Literal(l) => {
787 Some(AlgebraTerm::Literal(core_literal_to_algebra(l)))
788 }
789 oxirs_core::model::Object::BlankNode(b) => {
790 Some(AlgebraTerm::BlankNode(b.as_str().to_string()))
791 }
792 oxirs_core::model::Object::Variable(v) => Some(AlgebraTerm::Variable(v.clone())),
793 oxirs_core::model::Object::QuotedTriple(_) => None,
794 })
795 .collect();
796 Ok(objects.into_iter().collect())
797 }
798}
799
800fn wildcard_pattern() -> TriplePattern {
802 TriplePattern::new(
803 AlgebraTerm::Variable(crate::algebra::Variable::new_unchecked("s")),
804 AlgebraTerm::Variable(crate::algebra::Variable::new_unchecked("p")),
805 AlgebraTerm::Variable(crate::algebra::Variable::new_unchecked("o")),
806 )
807}
808
809pub struct GraphScopedDataset<'a> {
821 base: &'a dyn Dataset,
822 selector: GraphSelector,
823}
824
825impl<'a> GraphScopedDataset<'a> {
826 pub fn new(base: &'a dyn Dataset, selector: GraphSelector) -> Self {
828 Self { base, selector }
829 }
830}
831
832impl<'a> Dataset for GraphScopedDataset<'a> {
833 fn find_triples(
834 &self,
835 pattern: &TriplePattern,
836 ) -> Result<Vec<(AlgebraTerm, AlgebraTerm, AlgebraTerm)>> {
837 self.base.find_triples_in(&self.selector, pattern)
838 }
839
840 fn find_triples_in(
841 &self,
842 selector: &GraphSelector,
843 pattern: &TriplePattern,
844 ) -> Result<Vec<(AlgebraTerm, AlgebraTerm, AlgebraTerm)>> {
845 self.base.find_triples_in(selector, pattern)
847 }
848
849 fn has_graph_support(&self) -> bool {
850 self.base.has_graph_support()
851 }
852
853 fn named_graphs(&self) -> Result<Vec<AlgebraTerm>> {
854 self.base.named_graphs()
855 }
856
857 fn contains_triple(
858 &self,
859 subject: &AlgebraTerm,
860 predicate: &AlgebraTerm,
861 object: &AlgebraTerm,
862 ) -> Result<bool> {
863 let pattern = TriplePattern::new(subject.clone(), predicate.clone(), object.clone());
864 Ok(!self.find_triples(&pattern)?.is_empty())
865 }
866
867 fn subjects(&self) -> Result<Vec<AlgebraTerm>> {
868 let triples = self.find_triples(&wildcard_pattern())?;
869 let set: HashSet<_> = triples.into_iter().map(|(s, _, _)| s).collect();
870 Ok(set.into_iter().collect())
871 }
872
873 fn predicates(&self) -> Result<Vec<AlgebraTerm>> {
874 let triples = self.find_triples(&wildcard_pattern())?;
875 let set: HashSet<_> = triples.into_iter().map(|(_, p, _)| p).collect();
876 Ok(set.into_iter().collect())
877 }
878
879 fn objects(&self) -> Result<Vec<AlgebraTerm>> {
880 let triples = self.find_triples(&wildcard_pattern())?;
881 let set: HashSet<_> = triples.into_iter().map(|(_, _, o)| o).collect();
882 Ok(set.into_iter().collect())
883 }
884}
885
886pub struct DatasetView<'a> {
902 base: &'a dyn Dataset,
903 default_graphs: Vec<NamedNode>,
904 named_graphs: Vec<NamedNode>,
905}
906
907impl<'a> DatasetView<'a> {
908 pub fn new(
910 base: &'a dyn Dataset,
911 default_graphs: Vec<NamedNode>,
912 named_graphs: Vec<NamedNode>,
913 ) -> Self {
914 Self {
915 base,
916 default_graphs,
917 named_graphs,
918 }
919 }
920
921 fn is_passthrough(&self) -> bool {
924 self.default_graphs.is_empty() && self.named_graphs.is_empty()
925 }
926
927 fn default_graph_triples(
930 &self,
931 pattern: &TriplePattern,
932 ) -> Result<Vec<(AlgebraTerm, AlgebraTerm, AlgebraTerm)>> {
933 if self.default_graphs.is_empty() {
934 return self.base.find_triples(pattern);
935 }
936 let mut seen: HashSet<(AlgebraTerm, AlgebraTerm, AlgebraTerm)> = HashSet::new();
937 let mut out: Vec<(AlgebraTerm, AlgebraTerm, AlgebraTerm)> = Vec::new();
938 for graph in &self.default_graphs {
939 let selector = GraphSelector::Named(graph.clone());
940 for triple in self.base.find_triples_in(&selector, pattern)? {
941 if seen.insert(triple.clone()) {
942 out.push(triple);
943 }
944 }
945 }
946 Ok(out)
947 }
948}
949
950pub fn with_dataset_clause<'a>(
956 base: &'a dyn Dataset,
957 clause: &crate::query::DatasetClause,
958) -> DatasetView<'a> {
959 DatasetView::new(
960 base,
961 clause.default_graphs.clone(),
962 clause.named_graphs.clone(),
963 )
964}
965
966impl<'a> Dataset for DatasetView<'a> {
967 fn find_triples(
968 &self,
969 pattern: &TriplePattern,
970 ) -> Result<Vec<(AlgebraTerm, AlgebraTerm, AlgebraTerm)>> {
971 if self.is_passthrough() {
972 return self.base.find_triples(pattern);
973 }
974 self.default_graph_triples(pattern)
975 }
976
977 fn find_triples_in(
978 &self,
979 selector: &GraphSelector,
980 pattern: &TriplePattern,
981 ) -> Result<Vec<(AlgebraTerm, AlgebraTerm, AlgebraTerm)>> {
982 if self.is_passthrough() {
983 return self.base.find_triples_in(selector, pattern);
984 }
985 match selector {
986 GraphSelector::DefaultGraph => self.default_graph_triples(pattern),
987 GraphSelector::Named(graph) => {
988 if self.named_graphs.iter().any(|n| n == graph) {
990 self.base.find_triples_in(selector, pattern)
991 } else {
992 Ok(Vec::new())
993 }
994 }
995 }
996 }
997
998 fn has_graph_support(&self) -> bool {
999 self.base.has_graph_support()
1000 }
1001
1002 fn named_graphs(&self) -> Result<Vec<AlgebraTerm>> {
1003 if self.is_passthrough() {
1004 return self.base.named_graphs();
1005 }
1006 Ok(self
1007 .named_graphs
1008 .iter()
1009 .cloned()
1010 .map(AlgebraTerm::Iri)
1011 .collect())
1012 }
1013
1014 fn contains_triple(
1015 &self,
1016 subject: &AlgebraTerm,
1017 predicate: &AlgebraTerm,
1018 object: &AlgebraTerm,
1019 ) -> Result<bool> {
1020 let pattern = TriplePattern::new(subject.clone(), predicate.clone(), object.clone());
1021 Ok(!self.find_triples(&pattern)?.is_empty())
1022 }
1023
1024 fn subjects(&self) -> Result<Vec<AlgebraTerm>> {
1025 if self.is_passthrough() {
1026 return self.base.subjects();
1027 }
1028 let triples = self.default_graph_triples(&wildcard_pattern())?;
1029 let set: HashSet<_> = triples.into_iter().map(|(s, _, _)| s).collect();
1030 Ok(set.into_iter().collect())
1031 }
1032
1033 fn predicates(&self) -> Result<Vec<AlgebraTerm>> {
1034 if self.is_passthrough() {
1035 return self.base.predicates();
1036 }
1037 let triples = self.default_graph_triples(&wildcard_pattern())?;
1038 let set: HashSet<_> = triples.into_iter().map(|(_, p, _)| p).collect();
1039 Ok(set.into_iter().collect())
1040 }
1041
1042 fn objects(&self) -> Result<Vec<AlgebraTerm>> {
1043 if self.is_passthrough() {
1044 return self.base.objects();
1045 }
1046 let triples = self.default_graph_triples(&wildcard_pattern())?;
1047 let set: HashSet<_> = triples.into_iter().map(|(_, _, o)| o).collect();
1048 Ok(set.into_iter().collect())
1049 }
1050}
1051
1052#[cfg(test)]
1053mod store_ref_dataset_tests {
1054 use super::*;
1055 use crate::algebra::{Literal, Variable};
1056 use oxirs_core::model::{GraphName, Literal as CoreLiteral, NamedNode, Quad};
1057 use oxirs_core::rdf_store::{ConcreteStore, Store};
1058
1059 #[test]
1060 fn store_ref_dataset_matches_typed_literal() {
1061 let store = ConcreteStore::new().expect("store");
1062 let s = NamedNode::new_unchecked("http://ex/s");
1063 let age = NamedNode::new_unchecked("http://ex/age");
1064 let xsd_int = NamedNode::new_unchecked("http://www.w3.org/2001/XMLSchema#integer");
1065
1066 store
1068 .insert(&Quad::new(
1069 s.clone(),
1070 age.clone(),
1071 CoreLiteral::new_typed("25", xsd_int.clone()),
1072 GraphName::DefaultGraph,
1073 ))
1074 .expect("insert typed");
1075 store
1076 .insert(&Quad::new(
1077 s.clone(),
1078 age.clone(),
1079 CoreLiteral::new("25"),
1080 GraphName::DefaultGraph,
1081 ))
1082 .expect("insert plain");
1083
1084 let ds = StoreRefDataset::new(&store);
1085 let pattern = TriplePattern {
1086 subject: AlgebraTerm::Variable(Variable::new_unchecked("s")),
1087 predicate: AlgebraTerm::Iri(age.clone()),
1088 object: AlgebraTerm::Literal(Literal {
1089 value: "25".to_string(),
1090 language: None,
1091 datatype: Some(xsd_int.clone()),
1092 }),
1093 };
1094 let results = ds.find_triples(&pattern).expect("find");
1095 assert_eq!(
1096 results.len(),
1097 1,
1098 "typed-literal pattern must match exactly the typed triple"
1099 );
1100 match &results[0].2 {
1103 AlgebraTerm::Literal(lit) => {
1104 assert_eq!(lit.value, "25");
1105 assert_eq!(
1106 lit.datatype.as_ref().map(|d| d.as_str()),
1107 Some("http://www.w3.org/2001/XMLSchema#integer")
1108 );
1109 }
1110 other => panic!("expected literal, got {other:?}"),
1111 }
1112 }
1113
1114 #[test]
1115 fn store_ref_dataset_preserves_language_tag() {
1116 let store = ConcreteStore::new().expect("store");
1117 let s = NamedNode::new_unchecked("http://ex/s");
1118 let label = NamedNode::new_unchecked("http://ex/label");
1119 store
1120 .insert(&Quad::new(
1121 s.clone(),
1122 label.clone(),
1123 CoreLiteral::new_language_tagged_literal("hi", "en").expect("lang literal"),
1124 GraphName::DefaultGraph,
1125 ))
1126 .expect("insert");
1127
1128 let ds = StoreRefDataset::new(&store);
1129 let pattern = TriplePattern {
1130 subject: AlgebraTerm::Variable(Variable::new_unchecked("s")),
1131 predicate: AlgebraTerm::Iri(label.clone()),
1132 object: AlgebraTerm::Literal(Literal {
1133 value: "hi".to_string(),
1134 language: Some("en".to_string()),
1135 datatype: None,
1136 }),
1137 };
1138 let results = ds.find_triples(&pattern).expect("find");
1139 assert_eq!(results.len(), 1, "language-tagged pattern must match");
1140 match &results[0].2 {
1141 AlgebraTerm::Literal(lit) => {
1142 assert_eq!(lit.language.as_deref(), Some("en"));
1143 }
1144 other => panic!("expected literal, got {other:?}"),
1145 }
1146 }
1147}
1148
1149#[cfg(test)]
1150mod graph_scoping_tests {
1151 use super::*;
1152 use crate::algebra::Variable;
1153 use oxirs_core::model::{GraphName, NamedNode, Quad};
1154 use oxirs_core::rdf_store::{ConcreteStore, Store};
1155
1156 fn nn(s: &str) -> NamedNode {
1157 NamedNode::new_unchecked(s)
1158 }
1159
1160 fn wildcard() -> TriplePattern {
1161 TriplePattern {
1162 subject: AlgebraTerm::Variable(Variable::new_unchecked("s")),
1163 predicate: AlgebraTerm::Variable(Variable::new_unchecked("p")),
1164 object: AlgebraTerm::Variable(Variable::new_unchecked("o")),
1165 }
1166 }
1167
1168 fn seeded_store() -> ConcreteStore {
1171 let store = ConcreteStore::new().expect("store");
1172 let p = nn("http://ex/p");
1173 store
1174 .insert(&Quad::new(
1175 nn("http://ex/sd"),
1176 p.clone(),
1177 nn("http://ex/od"),
1178 GraphName::DefaultGraph,
1179 ))
1180 .expect("insert default");
1181 store
1182 .insert(&Quad::new(
1183 nn("http://ex/s1"),
1184 p.clone(),
1185 nn("http://ex/o1"),
1186 GraphName::NamedNode(nn("http://ex/g1")),
1187 ))
1188 .expect("insert g1 a");
1189 store
1190 .insert(&Quad::new(
1191 nn("http://ex/s2"),
1192 p.clone(),
1193 nn("http://ex/o2"),
1194 GraphName::NamedNode(nn("http://ex/g1")),
1195 ))
1196 .expect("insert g1 b");
1197 store
1198 .insert(&Quad::new(
1199 nn("http://ex/s3"),
1200 p,
1201 nn("http://ex/o3"),
1202 GraphName::NamedNode(nn("http://ex/g2")),
1203 ))
1204 .expect("insert g2");
1205 store
1206 }
1207
1208 #[test]
1209 fn store_ref_find_triples_reads_default_graph_only() {
1210 let store = seeded_store();
1211 let ds = StoreRefDataset::new(&store);
1212 let all = ds.find_triples(&wildcard()).expect("find default");
1215 assert_eq!(
1216 all.len(),
1217 1,
1218 "plain find_triples must be default-graph-only"
1219 );
1220 assert_eq!(all[0].0, AlgebraTerm::Iri(nn("http://ex/sd")));
1221 }
1222
1223 #[test]
1224 fn store_ref_find_triples_in_named_graph() {
1225 let store = seeded_store();
1226 let ds = StoreRefDataset::new(&store);
1227 let g1 = ds
1228 .find_triples_in(&GraphSelector::Named(nn("http://ex/g1")), &wildcard())
1229 .expect("find g1");
1230 assert_eq!(g1.len(), 2, "named graph g1 has two triples");
1231 let g2 = ds
1232 .find_triples_in(&GraphSelector::Named(nn("http://ex/g2")), &wildcard())
1233 .expect("find g2");
1234 assert_eq!(g2.len(), 1, "named graph g2 has one triple");
1235 let dg = ds
1236 .find_triples_in(&GraphSelector::DefaultGraph, &wildcard())
1237 .expect("find default");
1238 assert_eq!(dg.len(), 1, "default graph has one triple");
1239 }
1240
1241 #[test]
1242 fn store_ref_named_graphs_enumerates() {
1243 let store = seeded_store();
1244 let ds = StoreRefDataset::new(&store);
1245 assert!(ds.has_graph_support());
1246 let mut graphs = ds.named_graphs().expect("named graphs");
1247 graphs.sort_by_key(|t| format!("{t:?}"));
1248 assert_eq!(graphs.len(), 2, "two distinct named graphs");
1249 assert!(graphs.contains(&AlgebraTerm::Iri(nn("http://ex/g1"))));
1250 assert!(graphs.contains(&AlgebraTerm::Iri(nn("http://ex/g2"))));
1251 }
1252
1253 #[test]
1254 fn default_impl_find_triples_in_fails_loud() {
1255 struct NoGraphDataset;
1258 impl Dataset for NoGraphDataset {
1259 fn find_triples(
1260 &self,
1261 _pattern: &TriplePattern,
1262 ) -> Result<Vec<(AlgebraTerm, AlgebraTerm, AlgebraTerm)>> {
1263 Ok(Vec::new())
1264 }
1265 fn contains_triple(
1266 &self,
1267 _s: &AlgebraTerm,
1268 _p: &AlgebraTerm,
1269 _o: &AlgebraTerm,
1270 ) -> Result<bool> {
1271 Ok(false)
1272 }
1273 fn subjects(&self) -> Result<Vec<AlgebraTerm>> {
1274 Ok(Vec::new())
1275 }
1276 fn predicates(&self) -> Result<Vec<AlgebraTerm>> {
1277 Ok(Vec::new())
1278 }
1279 fn objects(&self) -> Result<Vec<AlgebraTerm>> {
1280 Ok(Vec::new())
1281 }
1282 }
1283 let ds = NoGraphDataset;
1284 assert!(!ds.has_graph_support());
1285 assert!(ds
1286 .find_triples_in(&GraphSelector::Named(nn("http://ex/g")), &wildcard())
1287 .is_err());
1288 assert!(ds.named_graphs().is_err());
1289 }
1290
1291 #[test]
1292 fn dataset_view_from_single_graph_default() {
1293 let store = seeded_store();
1294 let base = StoreRefDataset::new(&store);
1295 let view = DatasetView::new(&base, vec![nn("http://ex/g1")], vec![]);
1297 let default = view.find_triples(&wildcard()).expect("view default");
1298 assert_eq!(default.len(), 2, "FROM <g1> default graph = g1's triples");
1299 assert!(view.named_graphs().expect("named").is_empty());
1301 }
1302
1303 #[test]
1304 fn dataset_view_from_multiple_graphs_union() {
1305 let store = seeded_store();
1306 let base = StoreRefDataset::new(&store);
1307 let view = DatasetView::new(&base, vec![nn("http://ex/g1"), nn("http://ex/g2")], vec![]);
1309 let default = view.find_triples(&wildcard()).expect("view default");
1310 assert_eq!(default.len(), 3, "FROM union of g1+g2 = three triples");
1311 }
1312
1313 #[test]
1314 fn dataset_view_from_named_restricts_visibility() {
1315 let store = seeded_store();
1316 let base = StoreRefDataset::new(&store);
1317 let view = DatasetView::new(&base, vec![], vec![nn("http://ex/g1")]);
1319 let named = view.named_graphs().expect("named graphs");
1320 assert_eq!(named, vec![AlgebraTerm::Iri(nn("http://ex/g1"))]);
1321 let g1 = view
1323 .find_triples_in(&GraphSelector::Named(nn("http://ex/g1")), &wildcard())
1324 .expect("find g1");
1325 assert_eq!(g1.len(), 2);
1326 let g2 = view
1328 .find_triples_in(&GraphSelector::Named(nn("http://ex/g2")), &wildcard())
1329 .expect("find g2");
1330 assert!(g2.is_empty(), "graph outside FROM NAMED must be invisible");
1331 let default = view.find_triples(&wildcard()).expect("view default");
1333 assert_eq!(default.len(), 1, "no FROM => store default graph");
1334 }
1335
1336 #[test]
1337 fn dataset_view_empty_clause_is_passthrough() {
1338 let store = seeded_store();
1339 let base = StoreRefDataset::new(&store);
1340 let view = DatasetView::new(&base, vec![], vec![]);
1341 let default = view.find_triples(&wildcard()).expect("view default");
1343 assert_eq!(
1344 default.len(),
1345 1,
1346 "empty clause preserves default-graph read"
1347 );
1348 let mut graphs = view.named_graphs().expect("named");
1349 graphs.sort_by_key(|t| format!("{t:?}"));
1350 assert_eq!(graphs.len(), 2, "empty clause preserves base named graphs");
1351 }
1352
1353 #[test]
1354 fn graph_scoped_dataset_routes_plain_lookup() {
1355 let store = seeded_store();
1356 let base = StoreRefDataset::new(&store);
1357 let scoped = GraphScopedDataset::new(&base, GraphSelector::Named(nn("http://ex/g1")));
1359 let triples = scoped.find_triples(&wildcard()).expect("scoped find");
1360 assert_eq!(triples.len(), 2, "scoped plain lookup reads g1 only");
1361 }
1362}