lsp_max/runtime/control_plane/semantic_graph/
store.rs1use oxigraph::model::Term;
12use oxigraph::sparql::{QueryResults, SparqlEvaluator};
13use oxigraph::store::Store;
14
15use super::{
16 snapshot::{GraphDigest, LawGraphSnapshot},
17 SemanticLawGraph,
18};
19
20pub struct OxigraphStore {
25 store: Store,
26}
27
28impl OxigraphStore {
29 pub fn new() -> Result<Self, String> {
31 Ok(Self {
32 store: Store::new().map_err(|e| e.to_string())?,
33 })
34 }
35}
36
37impl Default for OxigraphStore {
38 fn default() -> Self {
39 Self::new().expect("in-memory Oxigraph store must not fail")
40 }
41}
42
43impl SemanticLawGraph for OxigraphStore {
44 fn load_snapshot(&self, snapshot: LawGraphSnapshot) -> Result<GraphDigest, String> {
45 let digest = snapshot.digest();
46 self.store
47 .load_from_reader(oxigraph::io::RdfFormat::NQuads, snapshot.nquads.as_bytes())
48 .map_err(|e| e.to_string())?;
49 Ok(digest)
50 }
51
52 fn query_invariants(&self, scope: &str) -> Result<Vec<String>, String> {
53 let sparql = format!(
54 r#"SELECT ?inv WHERE {{
55 ?inv a <urn:lsp-max:andon:Invariant> ;
56 <urn:lsp-max:andon:scope> "{scope}" .
57 }}"#
58 );
59 run_select_first_binding(&self.store, &sparql)
60 }
61
62 fn query_witnesses(&self, claim_id: &str) -> Result<Vec<String>, String> {
63 let sparql = format!(
64 r#"SELECT ?w WHERE {{
65 <{claim_id}> <urn:lsp-max:andon:hasWitness> ?w .
66 }}"#
67 );
68 run_select_first_binding(&self.store, &sparql)
69 }
70
71 fn query_repairs(&self, diagnostic_code: &str) -> Result<Vec<String>, String> {
72 let sparql = format!(
73 r#"SELECT ?repair WHERE {{
74 ?repair a <urn:lsp-max:andon:Repair> ;
75 <urn:lsp-max:andon:forCode> "{diagnostic_code}" .
76 }}"#
77 );
78 run_select_first_binding(&self.store, &sparql)
79 }
80
81 fn import_lsif_snapshot(
83 &self,
84 lsif_path: &std::path::Path,
85 graph_name: &str,
86 ) -> anyhow::Result<super::lsif_import::LsifImportStats> {
87 super::lsif_import::import_lsif_into_graph(&self.store, lsif_path, graph_name)
88 }
89}
90
91fn run_select_first_binding(store: &Store, sparql: &str) -> Result<Vec<String>, String> {
96 let evaluator = SparqlEvaluator::new();
97 let query = evaluator.parse_query(sparql).map_err(|e| e.to_string())?;
98 let results = query.on_store(store).execute().map_err(|e| e.to_string())?;
99
100 match results {
101 QueryResults::Solutions(sols) => {
102 let mut out = Vec::new();
103 for sol in sols {
104 let sol = sol.map_err(|e| e.to_string())?;
105 if let Some(term) = sol.get(0) {
106 out.push(term_to_string(term));
107 }
108 }
109 Ok(out)
110 }
111 _ => Ok(Vec::new()),
112 }
113}
114
115fn term_to_string(term: &Term) -> String {
118 match term {
119 Term::NamedNode(n) => n.as_str().to_string(),
120 Term::BlankNode(b) => b.as_str().to_string(),
121 Term::Literal(l) => l.value().to_string(),
122 _ => String::new(), }
124}