1use std::collections::HashMap;
11
12#[derive(Debug, Clone, PartialEq, Eq, Hash)]
14pub struct RdfTriple {
15 pub subject: String,
16 pub predicate: String,
17 pub object: String,
18}
19
20impl RdfTriple {
21 pub fn new(s: &str, p: &str, o: &str) -> Self {
22 Self {
23 subject: s.to_string(),
24 predicate: p.to_string(),
25 object: o.to_string(),
26 }
27 }
28
29 pub fn matches(
32 &self,
33 subject: Option<&str>,
34 predicate: Option<&str>,
35 object: Option<&str>,
36 ) -> bool {
37 subject.map(|s| s == self.subject).unwrap_or(true)
38 && predicate.map(|p| p == self.predicate).unwrap_or(true)
39 && object.map(|o| o == self.object).unwrap_or(true)
40 }
41}
42
43#[derive(Debug, Clone)]
52pub struct GraphView {
53 pub name: String,
55 triples: Vec<RdfTriple>,
56 pub base_iri: Option<String>,
58}
59
60impl GraphView {
61 pub fn new(name: &str, triples: Vec<RdfTriple>) -> Self {
63 Self {
64 name: name.to_string(),
65 triples,
66 base_iri: None,
67 }
68 }
69
70 pub fn with_base_iri(mut self, base: &str) -> Self {
72 self.base_iri = Some(base.to_string());
73 self
74 }
75
76 pub fn triples(&self) -> &[RdfTriple] {
78 &self.triples
79 }
80
81 pub fn len(&self) -> usize {
83 self.triples.len()
84 }
85
86 pub fn is_empty(&self) -> bool {
87 self.triples.is_empty()
88 }
89
90 pub fn find(
92 &self,
93 subject: Option<&str>,
94 predicate: Option<&str>,
95 object: Option<&str>,
96 ) -> Vec<&RdfTriple> {
97 self.triples
98 .iter()
99 .filter(|t| t.matches(subject, predicate, object))
100 .collect()
101 }
102
103 pub fn contains(&self, triple: &RdfTriple) -> bool {
105 self.triples.contains(triple)
106 }
107
108 pub fn subjects(&self) -> Vec<&str> {
110 let mut v: Vec<&str> = self.triples.iter().map(|t| t.subject.as_str()).collect();
111 v.sort_unstable();
112 v.dedup();
113 v
114 }
115
116 pub fn predicates(&self) -> Vec<&str> {
118 let mut v: Vec<&str> = self.triples.iter().map(|t| t.predicate.as_str()).collect();
119 v.sort_unstable();
120 v.dedup();
121 v
122 }
123
124 pub fn objects(&self) -> Vec<&str> {
126 let mut v: Vec<&str> = self.triples.iter().map(|t| t.object.as_str()).collect();
127 v.sort_unstable();
128 v.dedup();
129 v
130 }
131
132 pub fn filter(
134 &self,
135 subject: Option<String>,
136 predicate: Option<String>,
137 object: Option<String>,
138 ) -> FilteredView {
139 FilteredView::new(self.clone(), subject, predicate, object)
140 }
141}
142
143#[derive(Debug, Clone)]
152pub struct FilteredView {
153 base: GraphView,
154 subject_filter: Option<String>,
155 predicate_filter: Option<String>,
156 object_filter: Option<String>,
157 materialized: Option<Vec<RdfTriple>>,
159}
160
161impl FilteredView {
162 pub fn new(
164 base: GraphView,
165 subject: Option<String>,
166 predicate: Option<String>,
167 object: Option<String>,
168 ) -> Self {
169 Self {
170 base,
171 subject_filter: subject,
172 predicate_filter: predicate,
173 object_filter: object,
174 materialized: None,
175 }
176 }
177
178 pub fn evaluate(&self) -> Vec<&RdfTriple> {
180 self.base.find(
181 self.subject_filter.as_deref(),
182 self.predicate_filter.as_deref(),
183 self.object_filter.as_deref(),
184 )
185 }
186
187 pub fn materialize(&mut self) -> &[RdfTriple] {
189 if self.materialized.is_none() {
190 self.materialized = Some(self.evaluate().into_iter().cloned().collect());
191 }
192 self.materialized.as_deref().unwrap_or(&[])
193 }
194
195 pub fn invalidate(&mut self) {
197 self.materialized = None;
198 }
199
200 pub fn is_cached(&self) -> bool {
202 self.materialized.is_some()
203 }
204
205 pub fn count(&self) -> usize {
207 self.evaluate().len()
208 }
209
210 pub fn graph_name(&self) -> &str {
212 &self.base.name
213 }
214
215 pub fn and_predicate(mut self, predicate: &str) -> Self {
217 self.predicate_filter = Some(predicate.to_string());
218 self.materialized = None;
219 self
220 }
221
222 pub fn and_object(mut self, object: &str) -> Self {
224 self.object_filter = Some(object.to_string());
225 self.materialized = None;
226 self
227 }
228}
229
230#[derive(Debug, Clone)]
239pub struct UnionView {
240 pub name: String,
242 graphs: Vec<GraphView>,
243 deduplicate: bool,
244}
245
246impl UnionView {
247 pub fn new(name: &str, graphs: Vec<GraphView>, deduplicate: bool) -> Self {
249 Self {
250 name: name.to_string(),
251 graphs,
252 deduplicate,
253 }
254 }
255
256 pub fn add_graph(&mut self, graph: GraphView) {
258 self.graphs.push(graph);
259 }
260
261 pub fn triples(&self) -> Vec<&RdfTriple> {
265 let mut result: Vec<&RdfTriple> = Vec::new();
266 for g in &self.graphs {
267 for t in g.triples() {
268 if self.deduplicate {
269 if !result.contains(&t) {
270 result.push(t);
271 }
272 } else {
273 result.push(t);
274 }
275 }
276 }
277 result
278 }
279
280 pub fn find(
282 &self,
283 subject: Option<&str>,
284 predicate: Option<&str>,
285 object: Option<&str>,
286 ) -> Vec<&RdfTriple> {
287 let mut result: Vec<&RdfTriple> = Vec::new();
288 for g in &self.graphs {
289 for t in g.find(subject, predicate, object) {
290 if self.deduplicate {
291 if !result.contains(&t) {
292 result.push(t);
293 }
294 } else {
295 result.push(t);
296 }
297 }
298 }
299 result
300 }
301
302 pub fn len(&self) -> usize {
304 self.triples().len()
305 }
306
307 pub fn is_empty(&self) -> bool {
308 self.graphs.iter().all(|g| g.is_empty())
309 }
310
311 pub fn graph_count(&self) -> usize {
313 self.graphs.len()
314 }
315
316 pub fn graph_names(&self) -> Vec<&str> {
318 self.graphs.iter().map(|g| g.name.as_str()).collect()
319 }
320}
321
322#[derive(Debug, Clone)]
333pub struct MergedView {
334 pub name: String,
336 default_graph: GraphView,
337 named_graphs: Vec<GraphView>,
338 deduplicate: bool,
339 materialized: Option<Vec<RdfTriple>>,
341}
342
343impl MergedView {
344 pub fn new(
346 name: &str,
347 default_graph: GraphView,
348 named_graphs: Vec<GraphView>,
349 deduplicate: bool,
350 ) -> Self {
351 Self {
352 name: name.to_string(),
353 default_graph,
354 named_graphs,
355 deduplicate,
356 materialized: None,
357 }
358 }
359
360 pub fn add_named_graph(&mut self, graph: GraphView) {
362 self.named_graphs.push(graph);
363 self.materialized = None;
364 }
365
366 pub fn triples(&self) -> Vec<RdfTriple> {
368 let mut result: Vec<RdfTriple> = self.default_graph.triples().to_vec();
369 for ng in &self.named_graphs {
370 for t in ng.triples() {
371 if self.deduplicate {
372 if !result.contains(t) {
373 result.push(t.clone());
374 }
375 } else {
376 result.push(t.clone());
377 }
378 }
379 }
380 result
381 }
382
383 pub fn materialize(&mut self) -> &[RdfTriple] {
385 if self.materialized.is_none() {
386 self.materialized = Some(self.triples());
387 }
388 self.materialized.as_deref().unwrap_or(&[])
389 }
390
391 pub fn invalidate(&mut self) {
393 self.materialized = None;
394 }
395
396 pub fn find(
398 &self,
399 subject: Option<&str>,
400 predicate: Option<&str>,
401 object: Option<&str>,
402 ) -> Vec<RdfTriple> {
403 self.triples()
404 .into_iter()
405 .filter(|t| t.matches(subject, predicate, object))
406 .collect()
407 }
408
409 pub fn len(&self) -> usize {
411 self.triples().len()
412 }
413
414 pub fn is_empty(&self) -> bool {
415 self.default_graph.is_empty() && self.named_graphs.iter().all(|g| g.is_empty())
416 }
417
418 pub fn source_summary(&self) -> HashMap<String, usize> {
420 let mut map = HashMap::new();
421 map.insert(self.default_graph.name.clone(), self.default_graph.len());
422 for ng in &self.named_graphs {
423 map.insert(ng.name.clone(), ng.len());
424 }
425 map
426 }
427}
428
429#[derive(Debug, Default)]
439pub struct ViewMaterializer {
440 graph_views: HashMap<String, GraphView>,
441 filtered_cache: HashMap<String, Vec<RdfTriple>>,
442 predicate_deps: HashMap<String, Vec<String>>, stale: std::collections::HashSet<String>,
444}
445
446impl ViewMaterializer {
447 pub fn new() -> Self {
448 Self::default()
449 }
450
451 pub fn register_graph_view(&mut self, view: GraphView, predicates: Vec<String>) {
453 let name = view.name.clone();
454 self.predicate_deps.insert(name.clone(), predicates);
455 self.graph_views.insert(name.clone(), view);
456 self.stale.remove(&name);
457 }
458
459 pub fn get_graph_view(&self, name: &str) -> Option<&GraphView> {
461 self.graph_views.get(name)
462 }
463
464 pub fn mark_stale_for_predicates(&mut self, changed_predicates: &[String]) -> Vec<String> {
466 let mut stale_views = Vec::new();
467 for (view_name, deps) in &self.predicate_deps {
468 let affected = deps.is_empty() || deps.iter().any(|d| changed_predicates.contains(d));
469 if affected {
470 self.stale.insert(view_name.clone());
471 stale_views.push(view_name.clone());
472 }
473 }
474 stale_views
475 }
476
477 pub fn refresh_view(&mut self, name: &str, fresh_triples: Vec<RdfTriple>) {
479 if let Some(view) = self.graph_views.get_mut(name) {
480 *view = GraphView::new(name, fresh_triples.clone());
481 }
482 self.filtered_cache.insert(name.to_string(), fresh_triples);
483 self.stale.remove(name);
484 }
485
486 pub fn is_stale(&self, name: &str) -> bool {
488 self.stale.contains(name)
489 }
490
491 pub fn stale_views(&self) -> Vec<&str> {
493 self.stale.iter().map(|s| s.as_str()).collect()
494 }
495}
496
497#[cfg(test)]
502mod tests {
503 use super::*;
504
505 fn make_triple(s: &str, p: &str, o: &str) -> RdfTriple {
506 RdfTriple::new(s, p, o)
507 }
508
509 fn make_view(name: &str, triples: &[(&str, &str, &str)]) -> GraphView {
510 GraphView::new(
511 name,
512 triples
513 .iter()
514 .map(|(s, p, o)| make_triple(s, p, o))
515 .collect(),
516 )
517 }
518
519 #[test]
522 fn test_rdf_triple_matches_wildcard() {
523 let t = make_triple("s", "p", "o");
524 assert!(t.matches(None, None, None));
525 }
526
527 #[test]
528 fn test_rdf_triple_matches_bound_subject() {
529 let t = make_triple("s", "p", "o");
530 assert!(t.matches(Some("s"), None, None));
531 assert!(!t.matches(Some("x"), None, None));
532 }
533
534 #[test]
535 fn test_rdf_triple_matches_all_bound() {
536 let t = make_triple("s", "p", "o");
537 assert!(t.matches(Some("s"), Some("p"), Some("o")));
538 assert!(!t.matches(Some("s"), Some("p"), Some("WRONG")));
539 }
540
541 #[test]
544 fn test_graph_view_empty() {
545 let v = GraphView::new("g", vec![]);
546 assert!(v.is_empty());
547 assert_eq!(v.len(), 0);
548 }
549
550 #[test]
551 fn test_graph_view_triples() {
552 let v = make_view("g", &[("s", "p", "o")]);
553 assert_eq!(v.triples().len(), 1);
554 }
555
556 #[test]
557 fn test_graph_view_find_by_predicate() {
558 let v = make_view("g", &[("alice", "knows", "bob"), ("alice", "age", "30")]);
559 let found = v.find(None, Some("knows"), None);
560 assert_eq!(found.len(), 1);
561 assert_eq!(found[0].object, "bob");
562 }
563
564 #[test]
565 fn test_graph_view_find_no_match() {
566 let v = make_view("g", &[("s", "p", "o")]);
567 let found = v.find(None, Some("noSuchPredicate"), None);
568 assert!(found.is_empty());
569 }
570
571 #[test]
572 fn test_graph_view_contains() {
573 let v = make_view("g", &[("s", "p", "o")]);
574 assert!(v.contains(&make_triple("s", "p", "o")));
575 assert!(!v.contains(&make_triple("s", "p", "X")));
576 }
577
578 #[test]
579 fn test_graph_view_subjects() {
580 let v = make_view(
581 "g",
582 &[("alice", "p", "o"), ("bob", "p", "o"), ("alice", "q", "x")],
583 );
584 let mut subjects = v.subjects();
585 subjects.sort();
586 assert_eq!(subjects, vec!["alice", "bob"]);
587 }
588
589 #[test]
590 fn test_graph_view_predicates() {
591 let v = make_view("g", &[("s", "p1", "o"), ("s", "p2", "o"), ("s", "p1", "x")]);
592 let mut preds = v.predicates();
593 preds.sort();
594 assert_eq!(preds, vec!["p1", "p2"]);
595 }
596
597 #[test]
598 fn test_graph_view_objects() {
599 let v = make_view("g", &[("s", "p", "o1"), ("s", "p", "o2")]);
600 let mut objs = v.objects();
601 objs.sort();
602 assert_eq!(objs, vec!["o1", "o2"]);
603 }
604
605 #[test]
606 fn test_graph_view_with_base_iri() {
607 let v = GraphView::new("g", vec![]).with_base_iri("http://example.org/");
608 assert_eq!(v.base_iri.as_deref(), Some("http://example.org/"));
609 }
610
611 #[test]
612 fn test_graph_view_filter_returns_filtered_view() {
613 let v = make_view("g", &[("s", "p", "o"), ("s", "q", "x")]);
614 let mut fv = v.filter(None, Some("p".to_string()), None);
615 let mats = fv.materialize();
616 assert_eq!(mats.len(), 1);
617 assert_eq!(mats[0].predicate, "p");
618 }
619
620 #[test]
623 fn test_filtered_view_evaluate_empty() {
624 let v = make_view("g", &[]);
625 let fv = FilteredView::new(v, None, None, None);
626 assert_eq!(fv.evaluate().len(), 0);
627 }
628
629 #[test]
630 fn test_filtered_view_evaluate_subject_filter() {
631 let v = make_view("g", &[("alice", "p", "o"), ("bob", "p", "o")]);
632 let fv = FilteredView::new(v, Some("alice".to_string()), None, None);
633 assert_eq!(fv.count(), 1);
634 }
635
636 #[test]
637 fn test_filtered_view_materialize() {
638 let v = make_view("g", &[("s", "p1", "o"), ("s", "p2", "o")]);
639 let mut fv = FilteredView::new(v, None, Some("p1".to_string()), None);
640 let result = fv.materialize();
641 assert_eq!(result.len(), 1);
642 assert!(fv.is_cached());
643 }
644
645 #[test]
646 fn test_filtered_view_invalidate() {
647 let v = make_view("g", &[("s", "p", "o")]);
648 let mut fv = FilteredView::new(v, None, None, None);
649 fv.materialize();
650 assert!(fv.is_cached());
651 fv.invalidate();
652 assert!(!fv.is_cached());
653 }
654
655 #[test]
656 fn test_filtered_view_graph_name() {
657 let v = make_view("my_graph", &[]);
658 let fv = FilteredView::new(v, None, None, None);
659 assert_eq!(fv.graph_name(), "my_graph");
660 }
661
662 #[test]
663 fn test_filtered_view_and_predicate() {
664 let v = make_view("g", &[("s", "p1", "o"), ("s", "p2", "o")]);
665 let fv = FilteredView::new(v, None, None, None).and_predicate("p1");
666 assert_eq!(fv.count(), 1);
667 }
668
669 #[test]
670 fn test_filtered_view_and_object() {
671 let v = make_view("g", &[("s", "p", "o1"), ("s", "p", "o2")]);
672 let fv = FilteredView::new(v, None, None, None).and_object("o1");
673 assert_eq!(fv.count(), 1);
674 }
675
676 #[test]
679 fn test_union_view_empty_graphs() {
680 let uv = UnionView::new("u", vec![], false);
681 assert!(uv.is_empty());
682 assert_eq!(uv.len(), 0);
683 }
684
685 #[test]
686 fn test_union_view_single_graph() {
687 let g = make_view("g1", &[("s", "p", "o")]);
688 let uv = UnionView::new("u", vec![g], false);
689 assert_eq!(uv.len(), 1);
690 }
691
692 #[test]
693 fn test_union_view_two_graphs_no_dedup() {
694 let g1 = make_view("g1", &[("s", "p", "o")]);
695 let g2 = make_view("g2", &[("s", "p", "o")]);
696 let uv = UnionView::new("u", vec![g1, g2], false);
697 assert_eq!(uv.len(), 2);
698 }
699
700 #[test]
701 fn test_union_view_two_graphs_with_dedup() {
702 let g1 = make_view("g1", &[("s", "p", "o")]);
703 let g2 = make_view("g2", &[("s", "p", "o"), ("s2", "p2", "o2")]);
704 let uv = UnionView::new("u", vec![g1, g2], true);
705 assert_eq!(uv.len(), 2); }
707
708 #[test]
709 fn test_union_view_find() {
710 let g1 = make_view("g1", &[("alice", "knows", "bob")]);
711 let g2 = make_view("g2", &[("carol", "knows", "dave")]);
712 let uv = UnionView::new("u", vec![g1, g2], false);
713 let found = uv.find(None, Some("knows"), None);
714 assert_eq!(found.len(), 2);
715 }
716
717 #[test]
718 fn test_union_view_add_graph() {
719 let mut uv = UnionView::new("u", vec![], false);
720 assert_eq!(uv.graph_count(), 0);
721 uv.add_graph(make_view("g1", &[("s", "p", "o")]));
722 assert_eq!(uv.graph_count(), 1);
723 }
724
725 #[test]
726 fn test_union_view_graph_names() {
727 let g1 = make_view("graph_a", &[]);
728 let g2 = make_view("graph_b", &[]);
729 let uv = UnionView::new("u", vec![g1, g2], false);
730 let mut names = uv.graph_names();
731 names.sort();
732 assert_eq!(names, vec!["graph_a", "graph_b"]);
733 }
734
735 #[test]
738 fn test_merged_view_empty() {
739 let default_g = GraphView::new("default", vec![]);
740 let mv = MergedView::new("m", default_g, vec![], false);
741 assert!(mv.is_empty());
742 assert_eq!(mv.len(), 0);
743 }
744
745 #[test]
746 fn test_merged_view_default_only() {
747 let default_g = make_view("default", &[("s", "p", "o")]);
748 let mv = MergedView::new("m", default_g, vec![], false);
749 assert_eq!(mv.len(), 1);
750 }
751
752 #[test]
753 fn test_merged_view_named_graphs() {
754 let default_g = make_view("default", &[("s1", "p", "o1")]);
755 let ng = make_view("named", &[("s2", "p", "o2")]);
756 let mv = MergedView::new("m", default_g, vec![ng], false);
757 assert_eq!(mv.len(), 2);
758 }
759
760 #[test]
761 fn test_merged_view_dedup() {
762 let default_g = make_view("default", &[("s", "p", "o")]);
763 let ng = make_view("named", &[("s", "p", "o"), ("s2", "p2", "o2")]);
764 let mv = MergedView::new("m", default_g, vec![ng], true);
765 assert_eq!(mv.len(), 2); }
767
768 #[test]
769 fn test_merged_view_find() {
770 let default_g = make_view("default", &[("alice", "type", "Person")]);
771 let ng = make_view("named", &[("bob", "type", "Animal")]);
772 let mv = MergedView::new("m", default_g, vec![ng], false);
773 let found = mv.find(None, Some("type"), None);
774 assert_eq!(found.len(), 2);
775 }
776
777 #[test]
778 fn test_merged_view_materialize() {
779 let default_g = make_view("default", &[("s", "p", "o")]);
780 let mut mv = MergedView::new("m", default_g, vec![], false);
781 let mats = mv.materialize();
782 assert_eq!(mats.len(), 1);
783 }
784
785 #[test]
786 fn test_merged_view_invalidate() {
787 let default_g = make_view("default", &[("s", "p", "o")]);
788 let mut mv = MergedView::new("m", default_g, vec![], false);
789 mv.materialize();
790 mv.invalidate();
791 let mats = mv.materialize();
793 assert_eq!(mats.len(), 1);
794 }
795
796 #[test]
797 fn test_merged_view_source_summary() {
798 let default_g = make_view("default", &[("s", "p", "o")]);
799 let ng = make_view("ng1", &[("s2", "p2", "o2"), ("s3", "p3", "o3")]);
800 let mv = MergedView::new("m", default_g, vec![ng], false);
801 let summary = mv.source_summary();
802 assert_eq!(*summary.get("default").expect("key should exist"), 1);
803 assert_eq!(*summary.get("ng1").expect("key should exist"), 2);
804 }
805
806 #[test]
807 fn test_merged_view_add_named_graph() {
808 let default_g = make_view("default", &[("s", "p", "o")]);
809 let mut mv = MergedView::new("m", default_g, vec![], false);
810 assert_eq!(mv.len(), 1);
811 mv.add_named_graph(make_view("extra", &[("s2", "p2", "o2")]));
812 assert_eq!(mv.len(), 2);
813 }
814
815 #[test]
818 fn test_view_materializer_empty() {
819 let vm = ViewMaterializer::new();
820 assert!(vm.stale_views().is_empty());
821 }
822
823 #[test]
824 fn test_view_materializer_register_and_get() {
825 let mut vm = ViewMaterializer::new();
826 let view = make_view("v1", &[("s", "p", "o")]);
827 vm.register_graph_view(view, vec!["p".to_string()]);
828 assert!(vm.get_graph_view("v1").is_some());
829 }
830
831 #[test]
832 fn test_view_materializer_mark_stale() {
833 let mut vm = ViewMaterializer::new();
834 let view = make_view("v1", &[]);
835 vm.register_graph_view(view, vec!["http://p/age".to_string()]);
836 let stale = vm.mark_stale_for_predicates(&["http://p/age".to_string()]);
837 assert_eq!(stale.len(), 1);
838 assert!(vm.is_stale("v1"));
839 }
840
841 #[test]
842 fn test_view_materializer_no_stale_on_unrelated_predicate() {
843 let mut vm = ViewMaterializer::new();
844 let view = make_view("v1", &[]);
845 vm.register_graph_view(view, vec!["http://p/name".to_string()]);
846 let stale = vm.mark_stale_for_predicates(&["http://p/age".to_string()]);
847 assert!(stale.is_empty());
848 assert!(!vm.is_stale("v1"));
849 }
850
851 #[test]
852 fn test_view_materializer_refresh_clears_stale() {
853 let mut vm = ViewMaterializer::new();
854 let view = make_view("v1", &[]);
855 vm.register_graph_view(view, vec!["p".to_string()]);
856 vm.mark_stale_for_predicates(&["p".to_string()]);
857 assert!(vm.is_stale("v1"));
858 vm.refresh_view("v1", vec![make_triple("s", "p", "o")]);
859 assert!(!vm.is_stale("v1"));
860 }
861
862 #[test]
863 fn test_view_materializer_empty_deps_always_stale() {
864 let mut vm = ViewMaterializer::new();
865 let view = make_view("v_all", &[]);
866 vm.register_graph_view(view, vec![]); let stale = vm.mark_stale_for_predicates(&["any_predicate".to_string()]);
868 assert!(stale.contains(&"v_all".to_string()));
869 }
870}