Skip to main content

qualia_core_db/inference/runtime/graph_assist/
query.rs

1use crate::NQuin;
2
3use super::identity::{derive_prefix_identity, PrefixIdentity};
4
5#[repr(C)]
6#[derive(Clone, Copy, Debug, PartialEq, Eq)]
7pub struct GraphQuery {
8    /// Zero is a wildcard.
9    pub context: u64,
10    /// Zero is a wildcard.
11    pub subject: u64,
12    /// Zero is a wildcard.
13    pub predicate: u64,
14    /// Zero is a wildcard.
15    pub object: u64,
16}
17
18#[repr(C)]
19#[derive(Clone, Copy, Debug, PartialEq, Eq)]
20pub struct GraphAssistPolicy {
21    pub max_facts: u32,
22    pub sensitivity_ceiling: u8,
23    pub require_valid_parity: bool,
24    pub _pad: [u8; 2],
25}
26
27impl Default for GraphAssistPolicy {
28    fn default() -> Self {
29        Self {
30            max_facts: 64,
31            sensitivity_ceiling: 0,
32            require_valid_parity: true,
33            _pad: [0; 2],
34        }
35    }
36}
37
38#[derive(Clone, Copy, Debug, PartialEq, Eq)]
39pub enum GraphQueryError {
40    InvalidPolicy,
41    OutputBufferFull,
42}
43
44#[repr(C)]
45#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
46pub struct GraphSelectionReceipt {
47    pub scanned: u32,
48    pub matched: u32,
49    pub written: u32,
50    pub rejected_sensitivity: u32,
51    pub rejected_parity: u32,
52    pub prefix_identity: PrefixIdentity,
53}
54
55#[inline]
56fn matches(query: &GraphQuery, quin: &NQuin) -> bool {
57    (query.context == 0 || query.context == quin.context)
58        && (query.subject == 0 || query.subject == quin.subject)
59        && (query.predicate == 0 || query.predicate == quin.predicate)
60        && (query.object == 0 || query.object == quin.object)
61}
62
63/// Select graph facts directly from the flat Quin store into caller-owned storage.
64///
65/// This is the native inference query interface: hashed fields avoid SPARQL parsing and strings in
66/// the request path, while callers that need full SPARQL can resolve a graph slice before calling.
67pub fn query_graph_into(
68    graph: &[NQuin],
69    query: &GraphQuery,
70    policy: GraphAssistPolicy,
71    model_instance: u64,
72    tokenizer_revision: u64,
73    graph_revision: u64,
74    out: &mut [NQuin],
75) -> Result<GraphSelectionReceipt, GraphQueryError> {
76    if policy.max_facts == 0 {
77        return Err(GraphQueryError::InvalidPolicy);
78    }
79    let capacity = out.len().min(policy.max_facts as usize);
80    let mut receipt = GraphSelectionReceipt::default();
81    for quin in graph {
82        receipt.scanned = receipt.scanned.saturating_add(1);
83        if !matches(query, quin) {
84            continue;
85        }
86        let sensitivity = (quin.context >> 56) as u8;
87        if sensitivity > policy.sensitivity_ceiling {
88            receipt.rejected_sensitivity = receipt.rejected_sensitivity.saturating_add(1);
89            continue;
90        }
91        if policy.require_valid_parity
92            && quin.parity != (quin.subject ^ quin.predicate ^ quin.object ^ quin.context)
93        {
94            receipt.rejected_parity = receipt.rejected_parity.saturating_add(1);
95            continue;
96        }
97        receipt.matched = receipt.matched.saturating_add(1);
98        if receipt.written as usize >= capacity {
99            return Err(GraphQueryError::OutputBufferFull);
100        }
101        out[receipt.written as usize] = *quin;
102        receipt.written += 1;
103    }
104    receipt.prefix_identity = derive_prefix_identity(
105        model_instance,
106        tokenizer_revision,
107        query.context,
108        graph_revision,
109        &out[..receipt.written as usize],
110    );
111    Ok(receipt)
112}