Skip to main content

qualia_core_db/query/
resolve.rs

1//! Unified graph resolver — the consumer wiring for the hybrid-modality stack (#22).
2//!
3//! One entry point that composes the pieces built this session: it resolves an
4//! identifier's modal KIND (via the open identifier-kind fabric, `modal_kind`) and its
5//! outgoing relations, over either a maintained [`QuinIndex`] (O(1) point lookups) or a
6//! raw quin slice (O(n) scan, zero index build — for ad-hoc resolution against the live
7//! daemon graph snapshot). Lexical VALUES are recovered separately through the lexicon
8//! (with the collision backstop). This is what daemon / MCP / query callers route
9//! through, so identifier resolution has a single, consistent path.
10
11use crate::indexing::QuinIndex;
12use crate::modal_kind::{resolve_kind, HAS_MODALITY_KIND};
13use crate::NQuin;
14
15/// A resolved view of an identifier.
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub struct Resolved {
18    pub identifier: u64,
19    /// The modal identifier-kind, if asserted (`None` = a plain dictionary reference).
20    pub kind: Option<u64>,
21    /// Number of outgoing relations (its out-degree in the graph).
22    pub out_degree: usize,
23}
24
25/// Resolve over a maintained index — O(1) point lookups; preferred when an index exists
26/// (the per-cell cached path, task #22 step 3).
27pub fn resolve_in_index(index: &QuinIndex, identifier: u64) -> Resolved {
28    Resolved {
29        identifier,
30        kind: resolve_kind(index, identifier),
31        out_degree: index.iter_by_subject(identifier).count(),
32    }
33}
34
35/// Resolve over a raw quin slice — O(n) scan, no index build, no allocation. For ad-hoc
36/// single-identifier resolution against e.g. the daemon graph snapshot.
37pub fn resolve_in_slice(quins: &[NQuin], identifier: u64) -> Resolved {
38    let mut kind = None;
39    let mut out_degree = 0usize;
40    for q in quins {
41        if q.subject == identifier {
42            out_degree += 1;
43            if q.predicate == HAS_MODALITY_KIND {
44                kind = Some(q.object);
45            }
46        }
47    }
48    Resolved {
49        identifier,
50        kind,
51        out_degree,
52    }
53}
54
55/// The object of a specific relation over a slice (zero-alloc scan).
56pub fn related_in_slice(quins: &[NQuin], identifier: u64, predicate: u64) -> Option<u64> {
57    quins
58        .iter()
59        .find(|q| q.subject == identifier && q.predicate == predicate)
60        .map(|q| q.object)
61}
62
63#[cfg(test)]
64mod tests {
65    use super::*;
66    use crate::modal_kind::{tag_kind, KIND_CONTENT_HASH, KIND_WEBIZEN};
67    use crate::q_hash;
68
69    fn rel(s: u64, p: u64, o: u64) -> NQuin {
70        NQuin {
71            subject: s,
72            predicate: p,
73            object: o,
74            context: 0,
75            metadata: 0,
76            parity: 0,
77        }
78    }
79
80    #[test]
81    fn resolves_kind_and_out_degree_over_slice() {
82        let id = q_hash("https://example.org/thing");
83        let p1 = q_hash("p1");
84        let p2 = q_hash("p2");
85        let quins = [
86            tag_kind(id, KIND_WEBIZEN),
87            rel(id, p1, 10),
88            rel(id, p2, 20),
89            rel(q_hash("other"), p1, 99),
90        ];
91        let r = resolve_in_slice(&quins, id);
92        assert_eq!(r.kind, Some(KIND_WEBIZEN));
93        assert_eq!(r.out_degree, 3); // the kind quin + p1 + p2 (not the "other" subject)
94        assert_eq!(related_in_slice(&quins, id, p1), Some(10));
95    }
96
97    #[test]
98    fn index_and_slice_resolution_agree() {
99        let id: u64 = 0xF000_0000_0000_00AB; // a full-width identifier
100        let quins = [tag_kind(id, KIND_CONTENT_HASH), rel(id, q_hash("x"), 7)];
101        let idx = QuinIndex::from_slice(&quins);
102        assert_eq!(resolve_in_index(&idx, id), resolve_in_slice(&quins, id));
103        assert_eq!(resolve_in_index(&idx, id).kind, Some(KIND_CONTENT_HASH));
104    }
105
106    #[test]
107    fn unkinded_identifier_has_none_kind() {
108        let id = q_hash("plain");
109        let quins = [rel(id, q_hash("p"), 1)];
110        assert_eq!(resolve_in_slice(&quins, id).kind, None);
111        assert_eq!(resolve_in_slice(&quins, id).out_degree, 1);
112    }
113}