1use std::collections::{HashMap, HashSet, VecDeque};
34use thiserror::Error;
35
36#[derive(Debug, Error, Clone, PartialEq)]
38pub enum QlError {
39 #[error("Invalid axiom: {0}")]
40 InvalidAxiom(String),
41
42 #[error("Inconsistent ontology: {0}")]
43 Inconsistency(String),
44
45 #[error("Rewriting limit exceeded (max {0} reformulations)")]
46 RewritingLimitExceeded(usize),
47
48 #[error("Unsupported construct in OWL 2 QL: {0}")]
49 UnsupportedConstruct(String),
50}
51
52#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
55pub enum QlConcept {
56 Top,
58 Bottom,
60 Named(String),
62 SomeValuesTop { property: String },
64 SomeValuesTopInverse { property: String },
66}
67
68impl QlConcept {
69 pub fn named(iri: impl Into<String>) -> Self {
71 Self::Named(iri.into())
72 }
73
74 pub fn some_values_top(property: impl Into<String>) -> Self {
76 Self::SomeValuesTop {
77 property: property.into(),
78 }
79 }
80
81 pub fn some_values_top_inverse(property: impl Into<String>) -> Self {
83 Self::SomeValuesTopInverse {
84 property: property.into(),
85 }
86 }
87
88 pub fn as_named(&self) -> Option<&str> {
90 if let Self::Named(n) = self {
91 Some(n)
92 } else {
93 None
94 }
95 }
96
97 pub fn as_some_values_property(&self) -> Option<(&str, bool)> {
99 match self {
100 Self::SomeValuesTop { property } => Some((property, false)),
101 Self::SomeValuesTopInverse { property } => Some((property, true)),
102 _ => None,
103 }
104 }
105}
106
107#[derive(Debug, Clone, PartialEq, Eq, Hash)]
113pub enum ConceptExpr {
114 Atomic(QlConcept),
116 Union(Vec<ConceptExpr>),
118 Intersection(Vec<ConceptExpr>),
120}
121
122impl ConceptExpr {
123 pub fn named(iri: impl Into<String>) -> Self {
125 Self::Atomic(QlConcept::Named(iri.into()))
126 }
127
128 pub fn union_of(members: Vec<ConceptExpr>) -> Self {
130 Self::Union(members)
131 }
132
133 pub fn intersection_of(members: Vec<ConceptExpr>) -> Self {
135 Self::Intersection(members)
136 }
137
138 pub fn union_branches(&self) -> Vec<Vec<String>> {
149 match self {
150 ConceptExpr::Atomic(QlConcept::Named(n)) => vec![vec![n.clone()]],
151 ConceptExpr::Atomic(_) => vec![],
152 ConceptExpr::Union(members) => {
153 let all_names: Vec<String> =
155 members.iter().flat_map(|m| m.atomic_names()).collect();
156 if all_names.is_empty() {
157 vec![]
158 } else {
159 vec![all_names]
160 }
161 }
162 ConceptExpr::Intersection(members) => {
163 let names: Vec<String> = members.iter().flat_map(|m| m.atomic_names()).collect();
167 if names.is_empty() {
168 vec![]
169 } else {
170 vec![names]
171 }
172 }
173 }
174 }
175
176 pub fn atomic_names(&self) -> Vec<String> {
178 match self {
179 ConceptExpr::Atomic(QlConcept::Named(n)) => vec![n.clone()],
180 ConceptExpr::Atomic(_) => vec![],
181 ConceptExpr::Union(members) | ConceptExpr::Intersection(members) => {
182 members.iter().flat_map(|m| m.atomic_names()).collect()
183 }
184 }
185 }
186
187 pub fn has_union(&self) -> bool {
189 matches!(self, ConceptExpr::Union(_))
190 }
191}
192
193#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
195pub enum QlRole {
196 Named(String),
198 Inverse(String),
200}
201
202impl QlRole {
203 pub fn named(iri: impl Into<String>) -> Self {
204 Self::Named(iri.into())
205 }
206
207 pub fn inverse(iri: impl Into<String>) -> Self {
208 Self::Inverse(iri.into())
209 }
210
211 pub fn base_name(&self) -> &str {
213 match self {
214 Self::Named(n) => n,
215 Self::Inverse(n) => n,
216 }
217 }
218
219 pub fn is_inverse(&self) -> bool {
221 matches!(self, Self::Inverse(_))
222 }
223
224 pub fn inverse_role(&self) -> Self {
226 match self {
227 Self::Named(n) => Self::Inverse(n.clone()),
228 Self::Inverse(n) => Self::Named(n.clone()),
229 }
230 }
231}
232
233#[derive(Debug, Clone, PartialEq)]
235pub enum QlAxiom {
236 SubClassOf { sub: QlConcept, sup: QlConcept },
238
239 SubClassOfUnion {
243 sub: QlConcept,
244 sup_union: ConceptExpr,
245 },
246
247 EquivalentClasses(QlConcept, QlConcept),
249
250 SubObjectPropertyOf { sub: QlRole, sup: QlRole },
252
253 EquivalentObjectProperties(QlRole, QlRole),
255
256 InverseObjectProperties(String, String),
258
259 ObjectPropertyDomain { property: String, domain: String },
261
262 ObjectPropertyRange { property: String, range: String },
264
265 DisjointClasses(QlConcept, QlConcept),
267
268 DisjointObjectProperties(QlRole, QlRole),
270}
271
272#[derive(Debug, Clone, PartialEq, Eq, Hash)]
276pub enum QueryAtom {
277 TypeAtom {
279 individual: QueryTerm,
280 class: String,
281 },
282 PropertyAtom {
284 subject: QueryTerm,
285 property: String,
286 object: QueryTerm,
287 },
288}
289
290impl QueryAtom {
291 pub fn property_atom(
293 subject: QueryTerm,
294 property: impl Into<String>,
295 object: QueryTerm,
296 ) -> Self {
297 Self::PropertyAtom {
298 subject,
299 property: property.into(),
300 object,
301 }
302 }
303
304 pub fn variables(&self) -> Vec<&str> {
306 match self {
307 Self::TypeAtom { individual, .. } => individual.as_variable().into_iter().collect(),
308 Self::PropertyAtom {
309 subject, object, ..
310 } => {
311 let mut vars = Vec::new();
312 if let Some(v) = subject.as_variable() {
313 vars.push(v);
314 }
315 if let Some(v) = object.as_variable() {
316 vars.push(v);
317 }
318 vars
319 }
320 }
321 }
322}
323
324#[derive(Debug, Clone, PartialEq, Eq, Hash)]
326pub enum QueryTerm {
327 Variable(String),
329 Constant(String),
331}
332
333impl QueryTerm {
334 pub fn var(name: impl Into<String>) -> Self {
335 Self::Variable(name.into())
336 }
337
338 pub fn constant(iri: impl Into<String>) -> Self {
339 Self::Constant(iri.into())
340 }
341
342 pub fn as_variable(&self) -> Option<&str> {
343 if let Self::Variable(v) = self {
344 Some(v)
345 } else {
346 None
347 }
348 }
349
350 pub fn as_constant(&self) -> Option<&str> {
351 if let Self::Constant(c) = self {
352 Some(c)
353 } else {
354 None
355 }
356 }
357
358 pub fn is_variable(&self) -> bool {
359 matches!(self, Self::Variable(_))
360 }
361}
362
363#[derive(Debug, Clone, PartialEq, Eq)]
365pub struct ConjunctiveQuery {
366 pub atoms: Vec<QueryAtom>,
368 pub head_variables: Vec<String>,
370}
371
372impl ConjunctiveQuery {
373 pub fn new(atoms: Vec<QueryAtom>, head_variables: Vec<String>) -> Self {
374 Self {
375 atoms,
376 head_variables,
377 }
378 }
379
380 pub fn with_atoms(atoms: Vec<QueryAtom>) -> Self {
381 let head_variables = Self::collect_variables(&atoms);
382 Self {
383 atoms,
384 head_variables,
385 }
386 }
387
388 fn collect_variables(atoms: &[QueryAtom]) -> Vec<String> {
389 let mut seen = HashSet::new();
390 let mut vars = Vec::new();
391 for atom in atoms {
392 for v in atom.variables() {
393 if seen.insert(v.to_string()) {
394 vars.push(v.to_string());
395 }
396 }
397 }
398 vars
399 }
400}
401
402#[derive(Debug, Clone)]
404pub struct RewrittenQuery {
405 pub queries: Vec<ConjunctiveQuery>,
407}
408
409impl RewrittenQuery {
410 pub fn new() -> Self {
411 Self {
412 queries: Vec::new(),
413 }
414 }
415
416 pub fn add(&mut self, cq: ConjunctiveQuery) {
417 self.queries.push(cq);
418 }
419
420 pub fn len(&self) -> usize {
421 self.queries.len()
422 }
423
424 pub fn is_empty(&self) -> bool {
425 self.queries.is_empty()
426 }
427}
428
429impl Default for RewrittenQuery {
430 fn default() -> Self {
431 Self::new()
432 }
433}
434
435#[derive(Debug, Clone)]
440pub struct UnionAxiomEntry {
441 pub sub_class: String,
443 pub disjuncts: Vec<String>,
446}
447
448pub struct Owl2QLTBox {
450 axioms: Vec<QlAxiom>,
451 class_supers: HashMap<String, HashSet<String>>,
453 class_subs: HashMap<String, HashSet<String>>,
455 prop_supers: HashMap<String, HashSet<String>>,
457 inv_prop_supers: HashMap<String, HashSet<String>>,
459 inverse_of: HashMap<String, HashSet<String>>,
461 domain_classes: HashMap<String, HashSet<String>>,
464 range_classes: HashMap<String, HashSet<String>>,
466 disjoint_classes: HashSet<(String, String)>,
468 union_axioms: HashMap<String, Vec<Vec<String>>>,
471 union_rev_index: HashMap<String, HashSet<String>>,
474}
475
476impl Owl2QLTBox {
477 pub fn new() -> Self {
479 Self {
480 axioms: Vec::new(),
481 class_supers: HashMap::new(),
482 class_subs: HashMap::new(),
483 prop_supers: HashMap::new(),
484 inv_prop_supers: HashMap::new(),
485 inverse_of: HashMap::new(),
486 domain_classes: HashMap::new(),
487 range_classes: HashMap::new(),
488 disjoint_classes: HashSet::new(),
489 union_axioms: HashMap::new(),
490 union_rev_index: HashMap::new(),
491 }
492 }
493
494 pub fn add_axiom(&mut self, axiom: QlAxiom) {
496 self.axioms.push(axiom);
497 }
498
499 pub fn add_axioms(&mut self, axioms: impl IntoIterator<Item = QlAxiom>) {
501 for axiom in axioms {
502 self.add_axiom(axiom);
503 }
504 }
505
506 pub fn classify(&mut self) -> Result<(), QlError> {
508 self.expand_equivalences();
509 self.build_inverse_index();
510 self.compute_class_hierarchy()?;
511 self.compute_property_hierarchy()?;
512 self.compute_domain_range();
513 self.compute_disjointness();
514 self.compute_union_index();
515 Ok(())
516 }
517
518 fn expand_equivalences(&mut self) {
521 let mut extra = Vec::new();
522 for axiom in &self.axioms {
523 match axiom {
524 QlAxiom::EquivalentClasses(c1, c2) => {
525 extra.push(QlAxiom::SubClassOf {
526 sub: c1.clone(),
527 sup: c2.clone(),
528 });
529 extra.push(QlAxiom::SubClassOf {
530 sub: c2.clone(),
531 sup: c1.clone(),
532 });
533 }
534 QlAxiom::EquivalentObjectProperties(r1, r2) => {
535 extra.push(QlAxiom::SubObjectPropertyOf {
536 sub: r1.clone(),
537 sup: r2.clone(),
538 });
539 extra.push(QlAxiom::SubObjectPropertyOf {
540 sub: r2.clone(),
541 sup: r1.clone(),
542 });
543 }
544 QlAxiom::InverseObjectProperties(p1, p2) => {
545 extra.push(QlAxiom::SubObjectPropertyOf {
547 sub: QlRole::Named(p1.clone()),
548 sup: QlRole::Inverse(p2.clone()),
549 });
550 extra.push(QlAxiom::SubObjectPropertyOf {
551 sub: QlRole::Named(p2.clone()),
552 sup: QlRole::Inverse(p1.clone()),
553 });
554 }
555 _ => {}
556 }
557 }
558 self.axioms.extend(extra);
559 }
560
561 fn build_inverse_index(&mut self) {
562 for axiom in &self.axioms {
563 if let QlAxiom::InverseObjectProperties(p1, p2) = axiom {
564 self.inverse_of
565 .entry(p1.clone())
566 .or_default()
567 .insert(p2.clone());
568 self.inverse_of
569 .entry(p2.clone())
570 .or_default()
571 .insert(p1.clone());
572 }
573 }
574 }
575
576 fn compute_class_hierarchy(&mut self) -> Result<(), QlError> {
577 let mut direct: HashMap<String, HashSet<String>> = HashMap::new();
580
581 for axiom in &self.axioms {
582 if let QlAxiom::SubClassOf { sub, sup } = axiom {
583 if let (Some(sub_name), Some(sup_name)) = (sub.as_named(), sup.as_named()) {
586 direct
587 .entry(sub_name.to_string())
588 .or_default()
589 .insert(sup_name.to_string());
590 }
591 }
592 }
593
594 let all_classes: HashSet<String> = direct
596 .keys()
597 .chain(direct.values().flat_map(|s| s.iter()))
598 .cloned()
599 .collect();
600
601 for class in &all_classes {
602 let supers = Self::transitive_closure(class, &direct);
603 self.class_supers.insert(class.clone(), supers);
604 }
605
606 for (sub, supers) in &self.class_supers {
608 for sup in supers {
609 self.class_subs
610 .entry(sup.clone())
611 .or_default()
612 .insert(sub.clone());
613 }
614 }
615
616 Ok(())
617 }
618
619 fn compute_property_hierarchy(&mut self) -> Result<(), QlError> {
620 let mut named_direct: HashMap<String, HashSet<String>> = HashMap::new();
623 let mut inv_direct: HashMap<String, HashSet<String>> = HashMap::new();
625
626 for axiom in &self.axioms {
627 if let QlAxiom::SubObjectPropertyOf { sub, sup } = axiom {
628 match (sub, sup) {
629 (QlRole::Named(p), QlRole::Named(q)) => {
630 named_direct.entry(p.clone()).or_default().insert(q.clone());
631 }
632 (QlRole::Inverse(p), QlRole::Named(q)) => {
633 inv_direct.entry(p.clone()).or_default().insert(q.clone());
635 }
636 (QlRole::Named(p), QlRole::Inverse(q)) => {
637 inv_direct.entry(p.clone()).or_default().insert(q.clone());
641 }
642 (QlRole::Inverse(p), QlRole::Inverse(q)) => {
643 named_direct.entry(q.clone()).or_default().insert(p.clone());
645 }
646 }
647 }
648 }
649
650 let all_named: HashSet<String> = named_direct
651 .keys()
652 .chain(named_direct.values().flat_map(|s| s.iter()))
653 .chain(inv_direct.keys())
654 .chain(inv_direct.values().flat_map(|s| s.iter()))
655 .cloned()
656 .collect();
657
658 for prop in &all_named {
659 let supers = Self::transitive_closure(prop, &named_direct);
660 self.prop_supers.insert(prop.clone(), supers);
661 }
662
663 for prop in &all_named {
664 let inv_supers = Self::transitive_closure(prop, &inv_direct);
665 self.inv_prop_supers.insert(prop.clone(), inv_supers);
666 }
667
668 Ok(())
669 }
670
671 fn compute_domain_range(&mut self) {
672 for axiom in &self.axioms {
673 match axiom {
674 QlAxiom::ObjectPropertyDomain { property, domain } => {
675 self.domain_classes
676 .entry(property.clone())
677 .or_default()
678 .insert(domain.clone());
679 }
680 QlAxiom::ObjectPropertyRange { property, range } => {
681 self.range_classes
682 .entry(property.clone())
683 .or_default()
684 .insert(range.clone());
685 }
686 QlAxiom::SubClassOf { sub, sup } => {
687 if let (QlConcept::SomeValuesTop { property }, Some(sup_name)) =
689 (sub, sup.as_named())
690 {
691 self.domain_classes
692 .entry(property.clone())
693 .or_default()
694 .insert(sup_name.to_string());
695 }
696 if let (QlConcept::SomeValuesTopInverse { property }, Some(sup_name)) =
698 (sub, sup.as_named())
699 {
700 self.range_classes
701 .entry(property.clone())
702 .or_default()
703 .insert(sup_name.to_string());
704 }
705 }
706 _ => {}
707 }
708 }
709
710 let props: Vec<String> = self.prop_supers.keys().cloned().collect();
713 for prop in props {
714 let supers: Vec<String> = self
715 .prop_supers
716 .get(&prop)
717 .cloned()
718 .unwrap_or_default()
719 .into_iter()
720 .collect();
721 for sup in supers {
722 let domains: Vec<String> = self
723 .domain_classes
724 .get(&sup)
725 .cloned()
726 .unwrap_or_default()
727 .into_iter()
728 .collect();
729 for d in domains {
730 self.domain_classes
731 .entry(prop.clone())
732 .or_default()
733 .insert(d);
734 }
735 let ranges: Vec<String> = self
736 .range_classes
737 .get(&sup)
738 .cloned()
739 .unwrap_or_default()
740 .into_iter()
741 .collect();
742 for r in ranges {
743 self.range_classes
744 .entry(prop.clone())
745 .or_default()
746 .insert(r);
747 }
748 }
749 }
750
751 let domain_entries: Vec<(String, Vec<String>)> = self
753 .domain_classes
754 .iter()
755 .map(|(k, v)| (k.clone(), v.iter().cloned().collect()))
756 .collect();
757 for (prop, domains) in domain_entries {
758 let mut extra = Vec::new();
759 for d in &domains {
760 if let Some(supers) = self.class_supers.get(d) {
761 extra.extend(supers.iter().cloned());
762 }
763 }
764 let entry = self.domain_classes.entry(prop).or_default();
765 for e in extra {
766 entry.insert(e);
767 }
768 }
769 let range_entries: Vec<(String, Vec<String>)> = self
770 .range_classes
771 .iter()
772 .map(|(k, v)| (k.clone(), v.iter().cloned().collect()))
773 .collect();
774 for (prop, ranges) in range_entries {
775 let mut extra = Vec::new();
776 for r in &ranges {
777 if let Some(supers) = self.class_supers.get(r) {
778 extra.extend(supers.iter().cloned());
779 }
780 }
781 let entry = self.range_classes.entry(prop).or_default();
782 for e in extra {
783 entry.insert(e);
784 }
785 }
786 }
787
788 fn compute_disjointness(&mut self) {
789 for axiom in &self.axioms {
790 if let QlAxiom::DisjointClasses(c1, c2) = axiom {
791 if let (Some(n1), Some(n2)) = (c1.as_named(), c2.as_named()) {
792 let (a, b) = if n1 <= n2 {
793 (n1.to_string(), n2.to_string())
794 } else {
795 (n2.to_string(), n1.to_string())
796 };
797 self.disjoint_classes.insert((a, b));
798 }
799 }
800 }
801 }
802
803 fn compute_union_index(&mut self) {
820 for axiom in &self.axioms {
821 if let QlAxiom::SubClassOfUnion { sub, sup_union } = axiom {
822 if let Some(sub_name) = sub.as_named() {
823 let branches = sup_union.union_branches();
824 for branch in &branches {
825 self.union_axioms
827 .entry(sub_name.to_string())
828 .or_default()
829 .push(branch.clone());
830 for disjunct in branch {
832 self.union_rev_index
833 .entry(disjunct.clone())
834 .or_default()
835 .insert(sub_name.to_string());
836 }
837 }
838 }
839 }
840 }
841 }
842
843 fn transitive_closure(
845 start: &str,
846 edges: &HashMap<String, HashSet<String>>,
847 ) -> HashSet<String> {
848 let mut visited = HashSet::new();
849 let mut queue = VecDeque::new();
850 if let Some(direct) = edges.get(start) {
851 for d in direct {
852 queue.push_back(d.clone());
853 }
854 }
855 while let Some(node) = queue.pop_front() {
856 if visited.insert(node.clone()) {
857 if let Some(nexts) = edges.get(&node) {
858 for n in nexts {
859 if !visited.contains(n) {
860 queue.push_back(n.clone());
861 }
862 }
863 }
864 }
865 }
866 visited
867 }
868
869 pub fn superclasses(&self, class: &str) -> HashSet<String> {
873 self.class_supers.get(class).cloned().unwrap_or_default()
874 }
875
876 pub fn subclasses(&self, class: &str) -> HashSet<String> {
878 self.class_subs.get(class).cloned().unwrap_or_default()
879 }
880
881 pub fn superproperties(&self, property: &str) -> HashSet<String> {
883 self.prop_supers.get(property).cloned().unwrap_or_default()
884 }
885
886 pub fn inverse_properties(&self, property: &str) -> HashSet<String> {
888 self.inverse_of.get(property).cloned().unwrap_or_default()
889 }
890
891 pub fn domain_of(&self, property: &str) -> HashSet<String> {
893 self.domain_classes
894 .get(property)
895 .cloned()
896 .unwrap_or_default()
897 }
898
899 pub fn range_of(&self, property: &str) -> HashSet<String> {
901 self.range_classes
902 .get(property)
903 .cloned()
904 .unwrap_or_default()
905 }
906
907 pub fn are_disjoint(&self, c1: &str, c2: &str) -> bool {
909 let (a, b) = if c1 <= c2 {
910 (c1.to_string(), c2.to_string())
911 } else {
912 (c2.to_string(), c1.to_string())
913 };
914 self.disjoint_classes.contains(&(a, b))
915 }
916
917 pub fn all_subsumed_by(&self, class: &str) -> HashSet<String> {
920 let mut result = HashSet::new();
921 result.insert(class.to_string());
922 if let Some(subs) = self.class_subs.get(class) {
923 result.extend(subs.iter().cloned());
924 }
925 result
926 }
927
928 pub fn all_subproperties_of(&self, property: &str, is_inverse: bool) -> Vec<QlRole> {
931 let mut result = Vec::new();
932 if is_inverse {
934 result.push(QlRole::Inverse(property.to_string()));
935 } else {
936 result.push(QlRole::Named(property.to_string()));
937 }
938
939 for axiom in &self.axioms {
941 if let QlAxiom::SubObjectPropertyOf { sub, sup } = axiom {
942 match (sub, sup) {
943 (QlRole::Named(q), QlRole::Named(p)) if p == property && !is_inverse => {
944 result.push(QlRole::Named(q.clone()));
945 }
946 (QlRole::Inverse(q), QlRole::Named(p)) if p == property && !is_inverse => {
947 result.push(QlRole::Inverse(q.clone()));
948 }
949 (QlRole::Named(q), QlRole::Inverse(p)) if p == property && is_inverse => {
950 result.push(QlRole::Named(q.clone()));
951 }
952 (QlRole::Inverse(q), QlRole::Inverse(p)) if p == property && is_inverse => {
953 result.push(QlRole::Inverse(q.clone()));
954 }
955 _ => {}
956 }
957 }
958 }
959
960 if !is_inverse {
962 if let Some(invs) = self.inverse_of.get(property) {
963 for inv in invs {
964 result.push(QlRole::Inverse(inv.clone()));
965 }
966 }
967 } else if let Some(invs) = self.inverse_of.get(property) {
968 for inv in invs {
969 result.push(QlRole::Named(inv.clone()));
970 }
971 }
972
973 result.sort();
975 result.dedup();
976 result
977 }
978
979 pub fn union_axiom_disjuncts(&self, sub_class: &str) -> Vec<Vec<String>> {
982 self.union_axioms
983 .get(sub_class)
984 .cloned()
985 .unwrap_or_default()
986 }
987
988 pub fn classes_with_union_disjunct(&self, class: &str) -> HashSet<String> {
991 self.union_rev_index.get(class).cloned().unwrap_or_default()
992 }
993
994 pub fn has_union_axioms(&self) -> bool {
996 !self.union_axioms.is_empty()
997 }
998}
999
1000impl Default for Owl2QLTBox {
1001 fn default() -> Self {
1002 Self::new()
1003 }
1004}
1005
1006pub struct QueryRewriter {
1010 tbox: Owl2QLTBox,
1011 max_rewrites: usize,
1013}
1014
1015impl QueryRewriter {
1016 pub fn new(tbox: Owl2QLTBox) -> Self {
1018 Self {
1019 tbox,
1020 max_rewrites: 10_000,
1021 }
1022 }
1023
1024 pub fn with_limit(tbox: Owl2QLTBox, max_rewrites: usize) -> Self {
1026 Self { tbox, max_rewrites }
1027 }
1028
1029 pub fn tbox(&self) -> &Owl2QLTBox {
1031 &self.tbox
1032 }
1033
1034 pub fn rewrite_query(&self, query: &ConjunctiveQuery) -> Result<RewrittenQuery, QlError> {
1045 let mut result = RewrittenQuery::new();
1052 let mut seen: HashSet<Vec<QueryAtom>> = HashSet::new();
1053 let mut worklist: VecDeque<ConjunctiveQuery> = VecDeque::new();
1054
1055 worklist.push_back(query.clone());
1056
1057 while let Some(current_cq) = worklist.pop_front() {
1058 let mut canonical = current_cq.atoms.clone();
1059 canonical.sort_by(|a, b| format!("{a:?}").cmp(&format!("{b:?}")));
1060
1061 if !seen.insert(canonical) {
1062 continue;
1063 }
1064
1065 for atom_idx in 0..current_cq.atoms.len() {
1067 let unfolded_atoms = self.unfold_atom(¤t_cq.atoms[atom_idx]);
1068
1069 for unfolded_atom in unfolded_atoms {
1070 if unfolded_atom == current_cq.atoms[atom_idx] {
1072 continue;
1073 }
1074
1075 let mut new_atoms = current_cq.atoms.clone();
1077 new_atoms[atom_idx] = unfolded_atom;
1078 let new_cq = ConjunctiveQuery {
1079 atoms: new_atoms,
1080 head_variables: current_cq.head_variables.clone(),
1081 };
1082
1083 let mut new_canonical = new_cq.atoms.clone();
1084 new_canonical.sort_by(|a, b| format!("{a:?}").cmp(&format!("{b:?}")));
1085
1086 if !seen.contains(&new_canonical) {
1087 if seen.len() + worklist.len() >= self.max_rewrites {
1088 return Err(QlError::RewritingLimitExceeded(self.max_rewrites));
1089 }
1090 worklist.push_back(new_cq);
1091 }
1092 }
1093
1094 if let QueryAtom::TypeAtom { individual, class } = ¤t_cq.atoms[atom_idx] {
1097 let union_branches = self.unfold_type_atom_union_branches(individual, class);
1098 for branch_atoms in union_branches {
1099 let mut new_atoms = current_cq.atoms.clone();
1100 new_atoms[atom_idx] = branch_atoms;
1101 let new_cq = ConjunctiveQuery {
1102 atoms: new_atoms,
1103 head_variables: current_cq.head_variables.clone(),
1104 };
1105 let mut new_canonical = new_cq.atoms.clone();
1106 new_canonical.sort_by(|a, b| format!("{a:?}").cmp(&format!("{b:?}")));
1107 if !seen.contains(&new_canonical) {
1108 if seen.len() + worklist.len() >= self.max_rewrites {
1109 return Err(QlError::RewritingLimitExceeded(self.max_rewrites));
1110 }
1111 worklist.push_back(new_cq);
1112 }
1113 }
1114 }
1115 }
1116
1117 result.add(current_cq);
1119 }
1120
1121 Ok(result)
1122 }
1123
1124 pub fn unfold_atom(&self, atom: &QueryAtom) -> Vec<QueryAtom> {
1127 match atom {
1128 QueryAtom::TypeAtom { individual, class } => self.unfold_type_atom(individual, class),
1129 QueryAtom::PropertyAtom {
1130 subject,
1131 property,
1132 object,
1133 } => self.unfold_property_atom(subject, property, object),
1134 }
1135 }
1136
1137 fn unfold_type_atom(&self, individual: &QueryTerm, class: &str) -> Vec<QueryAtom> {
1143 let mut result = Vec::new();
1144
1145 result.push(QueryAtom::TypeAtom {
1147 individual: individual.clone(),
1148 class: class.to_string(),
1149 });
1150
1151 let subsumed = self.tbox.all_subsumed_by(class);
1153 for sub in &subsumed {
1154 if sub != class {
1155 result.push(QueryAtom::TypeAtom {
1156 individual: individual.clone(),
1157 class: sub.clone(),
1158 });
1159 }
1160 }
1161
1162 let fresh_var = self.fresh_variable(individual, "dom");
1165 for (prop, domains) in &self.tbox.domain_classes {
1166 for domain in domains {
1167 if domain == class || self.tbox.superclasses(domain).contains(class) {
1169 result.push(QueryAtom::PropertyAtom {
1171 subject: individual.clone(),
1172 property: prop.clone(),
1173 object: fresh_var.clone(),
1174 });
1175 }
1176 }
1177 }
1178
1179 let fresh_var2 = self.fresh_variable(individual, "rng");
1182 for (prop, ranges) in &self.tbox.range_classes {
1183 for range in ranges {
1184 if range == class || self.tbox.superclasses(range).contains(class) {
1185 result.push(QueryAtom::PropertyAtom {
1187 subject: fresh_var2.clone(),
1188 property: prop.clone(),
1189 object: individual.clone(),
1190 });
1191 }
1192 }
1193 }
1194
1195 result.dedup_by(|a, b| format!("{a:?}") == format!("{b:?}"));
1197 result
1198 }
1199
1200 pub fn unfold_type_atom_union_branches(
1216 &self,
1217 individual: &QueryTerm,
1218 class: &str,
1219 ) -> Vec<QueryAtom> {
1220 let mut result = Vec::new();
1221
1222 let union_sources = self.tbox.classes_with_union_disjunct(class);
1224 for source_class in &union_sources {
1225 result.push(QueryAtom::TypeAtom {
1226 individual: individual.clone(),
1227 class: source_class.clone(),
1228 });
1229 }
1230
1231 let mut extra = Vec::new();
1233 for source_class in &union_sources {
1234 for sub in self.tbox.subclasses(source_class) {
1235 extra.push(QueryAtom::TypeAtom {
1236 individual: individual.clone(),
1237 class: sub,
1238 });
1239 }
1240 }
1241 result.extend(extra);
1242
1243 result.dedup_by(|a, b| format!("{a:?}") == format!("{b:?}"));
1245 result
1246 }
1247
1248 fn unfold_property_atom(
1254 &self,
1255 subject: &QueryTerm,
1256 property: &str,
1257 object: &QueryTerm,
1258 ) -> Vec<QueryAtom> {
1259 let mut result = Vec::new();
1260
1261 result.push(QueryAtom::PropertyAtom {
1263 subject: subject.clone(),
1264 property: property.to_string(),
1265 object: object.clone(),
1266 });
1267
1268 let sub_roles = self.tbox.all_subproperties_of(property, false);
1270 for role in sub_roles {
1271 match role {
1272 QlRole::Named(q) if q != property => {
1273 result.push(QueryAtom::PropertyAtom {
1274 subject: subject.clone(),
1275 property: q,
1276 object: object.clone(),
1277 });
1278 }
1279 QlRole::Inverse(q) => {
1280 result.push(QueryAtom::PropertyAtom {
1282 subject: object.clone(),
1283 property: q,
1284 object: subject.clone(),
1285 });
1286 }
1287 _ => {}
1288 }
1289 }
1290
1291 result.dedup_by(|a, b| format!("{a:?}") == format!("{b:?}"));
1293 result
1294 }
1295
1296 fn fresh_variable(&self, base: &QueryTerm, suffix: &str) -> QueryTerm {
1298 match base {
1299 QueryTerm::Variable(v) => QueryTerm::Variable(format!("_fresh_{v}_{suffix}")),
1300 QueryTerm::Constant(c) => {
1301 let short: String = c.chars().take(8).collect();
1303 QueryTerm::Variable(format!("_fresh_{short}_{suffix}"))
1304 }
1305 }
1306 }
1307
1308 pub fn is_satisfiable(&self, class: &str) -> bool {
1310 !self.tbox.are_disjoint(class, class)
1313 }
1314
1315 pub fn rewrite_query_union_aware(
1330 &self,
1331 query: &ConjunctiveQuery,
1332 ) -> Result<RewrittenQuery, QlError> {
1333 self.rewrite_query(query)
1335 }
1336
1337 pub fn union_siblings_for(&self, class: &str) -> Vec<Vec<String>> {
1345 let sources = self.tbox.classes_with_union_disjunct(class);
1347 let mut result = Vec::new();
1348 for source in &sources {
1349 let disjunct_lists = self.tbox.union_axiom_disjuncts(source);
1350 for disjuncts in disjunct_lists {
1351 if disjuncts.contains(&class.to_string()) {
1352 result.push(disjuncts);
1353 }
1354 }
1355 }
1356 result
1357 }
1358
1359 pub fn are_union_siblings(&self, class_a: &str, class_b: &str) -> bool {
1361 let siblings = self.union_siblings_for(class_a);
1362 siblings
1363 .iter()
1364 .any(|branch| branch.contains(&class_b.to_string()))
1365 }
1366
1367 pub fn conjunction_satisfiable(&self, classes: &[&str]) -> bool {
1373 for i in 0..classes.len() {
1375 for j in (i + 1)..classes.len() {
1376 if self.tbox.are_disjoint(classes[i], classes[j]) {
1377 return false;
1378 }
1379 }
1380 }
1381 true
1382 }
1383}
1384
1385pub fn build_tbox(axioms: Vec<QlAxiom>) -> Result<Owl2QLTBox, QlError> {
1389 let mut tbox = Owl2QLTBox::new();
1390 tbox.add_axioms(axioms);
1391 tbox.classify()?;
1392 Ok(tbox)
1393}
1394
1395pub fn rewrite_query(atoms: Vec<QueryAtom>, tbox: &Owl2QLTBox) -> Result<RewrittenQuery, QlError> {
1397 let cq = ConjunctiveQuery::with_atoms(atoms);
1398 let rewriter = QueryRewriter::new(tbox.clone());
1399 rewriter.rewrite_query(&cq)
1400}
1401
1402pub fn rewrite_query_union(
1404 atoms: Vec<QueryAtom>,
1405 tbox: &Owl2QLTBox,
1406) -> Result<RewrittenQuery, QlError> {
1407 let cq = ConjunctiveQuery::with_atoms(atoms);
1408 let rewriter = QueryRewriter::new(tbox.clone());
1409 rewriter.rewrite_query_union_aware(&cq)
1410}
1411
1412impl Clone for Owl2QLTBox {
1413 fn clone(&self) -> Self {
1414 let mut new_tbox = Owl2QLTBox::new();
1415 new_tbox.axioms = self.axioms.clone();
1416 new_tbox.class_supers = self.class_supers.clone();
1417 new_tbox.class_subs = self.class_subs.clone();
1418 new_tbox.prop_supers = self.prop_supers.clone();
1419 new_tbox.inv_prop_supers = self.inv_prop_supers.clone();
1420 new_tbox.inverse_of = self.inverse_of.clone();
1421 new_tbox.domain_classes = self.domain_classes.clone();
1422 new_tbox.range_classes = self.range_classes.clone();
1423 new_tbox.disjoint_classes = self.disjoint_classes.clone();
1424 new_tbox.union_axioms = self.union_axioms.clone();
1425 new_tbox.union_rev_index = self.union_rev_index.clone();
1426 new_tbox
1427 }
1428}
1429
1430#[cfg(test)]
1431mod tests {
1432 use super::*;
1433 include!("owl_ql_tests.rs");
1434 include!("owl_ql_tests_extended.rs");
1435}