Skip to main content

ontologos_ql/
query.rs

1//! Conjunctive query AST and evaluation.
2
3use crate::hierarchy::TaxonomyHierarchy;
4use ontologos_core::{EntityId, Ontology};
5
6use crate::{Error, Result};
7
8/// Built-in `owl:Nothing` IRI used by query rewrite.
9pub const OWL_NOTHING_IRI: &str = "http://www.w3.org/2002/07/owl#Nothing";
10
11fn lookup_query_class(ontology: &Ontology, class: &str) -> Result<EntityId> {
12    ontology
13        .lookup_entity(class)
14        .or_else(|| match class {
15            "owl:Nothing" => ontology.lookup_entity(OWL_NOTHING_IRI),
16            "owl:Thing" => ontology.lookup_entity("http://www.w3.org/2002/07/owl#Thing"),
17            _ => None,
18        })
19        .ok_or_else(|| Error::UnknownClass(class.to_owned()))
20}
21
22/// A conjunctive query atom: `Class(var)` or `SubClassOf(var, class)`.
23#[derive(Debug, Clone, PartialEq, Eq)]
24pub enum QueryAtom {
25    /// Individual/class variable has type `class`.
26    Type {
27        /// Variable name.
28        var: String,
29        /// Class IRI or local name.
30        class: String,
31    },
32    /// `var` is subsumed by named class.
33    Subsumed {
34        /// Variable name.
35        var: String,
36        /// Superclass IRI or local name.
37        superclass: String,
38    },
39}
40
41/// Conjunctive query (AND of atoms).
42#[derive(Debug, Clone, Default)]
43pub struct ConjunctiveQuery {
44    /// Query atoms.
45    pub atoms: Vec<QueryAtom>,
46}
47
48/// One answer binding (variable → entity id).
49#[derive(Debug, Clone, PartialEq, Eq)]
50pub struct QueryAnswer {
51    /// Variable bindings.
52    pub bindings: Vec<(String, EntityId)>,
53}
54
55/// Evaluate a CQ with a single type atom per variable (QL subset).
56pub fn evaluate(
57    engine: &TaxonomyHierarchy<'_>,
58    ontology: &Ontology,
59    query: &ConjunctiveQuery,
60) -> Result<Vec<QueryAnswer>> {
61    if query.atoms.len() > 1 {
62        return Err(crate::Error::Parse(
63            "conjunctive queries with more than one atom are not supported yet".into(),
64        ));
65    }
66    if query.atoms.is_empty() {
67        return Ok(Vec::new());
68    }
69    let atom = &query.atoms[0];
70    match atom {
71        QueryAtom::Type { var, class } => {
72            let class_id = lookup_query_class(ontology, class)?;
73            if engine.taxonomy().unsatisfiable.contains(&class_id) {
74                return Ok(Vec::new());
75            }
76            let subs = engine.direct_subclasses(class_id)?;
77            let mut answers = Vec::new();
78            for sub in subs {
79                answers.push(QueryAnswer {
80                    bindings: vec![(var.clone(), sub)],
81                });
82            }
83            Ok(answers)
84        }
85        QueryAtom::Subsumed { var, superclass } => {
86            let sup_id = lookup_query_class(ontology, superclass)?;
87            if engine.taxonomy().unsatisfiable.contains(&sup_id) {
88                return Ok(Vec::new());
89            }
90            let subs = engine.direct_subclasses(sup_id)?;
91            let mut answers = Vec::new();
92            for sub in subs {
93                answers.push(QueryAnswer {
94                    bindings: vec![(var.clone(), sub)],
95                });
96            }
97            Ok(answers)
98        }
99    }
100}