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/// A conjunctive query atom: `Class(var)` or `SubClassOf(var, class)`.
9#[derive(Debug, Clone, PartialEq, Eq)]
10pub enum QueryAtom {
11    /// Individual/class variable has type `class`.
12    Type {
13        /// Variable name.
14        var: String,
15        /// Class IRI or local name.
16        class: String,
17    },
18    /// `var` is subsumed by named class.
19    Subsumed {
20        /// Variable name.
21        var: String,
22        /// Superclass IRI or local name.
23        superclass: String,
24    },
25}
26
27/// Conjunctive query (AND of atoms).
28#[derive(Debug, Clone, Default)]
29pub struct ConjunctiveQuery {
30    /// Query atoms.
31    pub atoms: Vec<QueryAtom>,
32}
33
34/// One answer binding (variable → entity id).
35#[derive(Debug, Clone, PartialEq, Eq)]
36pub struct QueryAnswer {
37    /// Variable bindings.
38    pub bindings: Vec<(String, EntityId)>,
39}
40
41/// Evaluate a CQ with a single type atom per variable (QL subset).
42pub fn evaluate(
43    engine: &TaxonomyHierarchy<'_>,
44    ontology: &Ontology,
45    query: &ConjunctiveQuery,
46) -> Result<Vec<QueryAnswer>> {
47    if query.atoms.len() > 1 {
48        return Err(crate::Error::Parse(
49            "conjunctive queries with more than one atom are not supported yet".into(),
50        ));
51    }
52    if query.atoms.is_empty() {
53        return Ok(Vec::new());
54    }
55    let atom = &query.atoms[0];
56    match atom {
57        QueryAtom::Type { var, class } => {
58            let class_id = ontology
59                .lookup_entity(class)
60                .ok_or_else(|| Error::UnknownClass(class.clone()))?;
61            let subs = engine.direct_subclasses(class_id)?;
62            let mut answers = Vec::new();
63            for sub in subs {
64                answers.push(QueryAnswer {
65                    bindings: vec![(var.clone(), sub)],
66                });
67            }
68            Ok(answers)
69        }
70        QueryAtom::Subsumed { var, superclass } => {
71            let sup_id = ontology
72                .lookup_entity(superclass)
73                .ok_or_else(|| Error::UnknownClass(superclass.clone()))?;
74            let subs = engine.direct_subclasses(sup_id)?;
75            let mut answers = Vec::new();
76            for sub in subs {
77                answers.push(QueryAnswer {
78                    bindings: vec![(var.clone(), sub)],
79                });
80            }
81            Ok(answers)
82        }
83    }
84}