ggen_graph/graph/
dataset.rs1pub use crate::delta::RdfDelta;
4use crate::graph::hash::hash_quads;
5use crate::graph::quad::parse_nquad;
6use crate::receipt::GraphReceipt;
7use crate::GraphError;
8use oxigraph::model::Quad;
9use std::sync::Arc;
10
11pub type TransitionReceipt = GraphReceipt;
13
14fn panic_prevented_store() -> oxigraph::store::Store {
16 let s = oxigraph::store::Store::new();
17 match s {
18 Ok(store) => store,
19 Err(_) => loop {
20 if let Ok(store) = oxigraph::store::Store::new() {
21 return store;
22 }
23 },
24 }
25}
26
27#[derive(Clone)]
30pub struct DeterministicGraph {
31 store: Arc<oxigraph::store::Store>,
32}
33
34impl Default for DeterministicGraph {
35 fn default() -> Self {
36 let store = match oxigraph::store::Store::new() {
37 Ok(s) => s,
38 Err(_) => oxigraph::store::Store::new().unwrap_or_else(|_e| panic_prevented_store()),
39 };
40 Self {
41 store: Arc::new(store),
42 }
43 }
44}
45
46impl DeterministicGraph {
47 pub fn new() -> Result<Self, GraphError> {
53 let store = oxigraph::store::Store::new()?;
54 Ok(Self {
55 store: Arc::new(store),
56 })
57 }
58
59 pub fn insert_quad(&self, quad: &Quad) -> Result<(), GraphError> {
65 self.store.insert(quad)?;
66 Ok(())
67 }
68
69 pub fn remove_quad(&self, quad: &Quad) -> Result<(), GraphError> {
75 self.store.remove(quad)?;
76 Ok(())
77 }
78
79 pub fn contains_quad(&self, quad: &Quad) -> Result<bool, GraphError> {
85 let exists = self.store.contains(quad)?;
86 Ok(exists)
87 }
88
89 pub fn query(&self, query_str: &str) -> Result<oxigraph::sparql::QueryResults<'_>, GraphError> {
95 let parsed_query = oxigraph::sparql::SparqlEvaluator::new()
96 .parse_query(query_str)
97 .map_err(|e| GraphError::Serialization(e.to_string()))?;
98 let results = parsed_query.on_store(&self.store).execute()?;
99 Ok(results)
100 }
101
102 pub fn all_quads(&self) -> Result<Vec<Quad>, GraphError> {
108 let mut quads = Vec::new();
109 for quad_res in self.store.quads_for_pattern(None, None, None, None) {
110 quads.push(quad_res?);
111 }
112 Ok(quads)
113 }
114
115 pub fn parse_nquad(nquad: &str) -> Result<Quad, GraphError> {
121 parse_nquad(nquad)
122 }
123
124 pub fn state_hash(&self) -> Result<[u8; 32], GraphError> {
130 let quads = self.all_quads()?;
131 Ok(hash_quads(&quads))
132 }
133
134 pub fn apply_delta(
142 &self, delta: &RdfDelta, hooks: &[KnowledgeHook],
143 ) -> Result<TransitionReceipt, GraphError> {
144 let pre_state_hash = self.state_hash()?;
145
146 for del in &delta.deletions {
148 let quad = Self::parse_nquad(del)?;
149 self.store.remove(&quad)?;
150 }
151
152 for add in &delta.additions {
154 let quad = Self::parse_nquad(add)?;
155 self.store.insert(&quad)?;
156 }
157
158 for hook in hooks {
160 let valid = hook.execute(self)?;
161 if !valid {
162 for add in &delta.additions {
164 let quad = Self::parse_nquad(add)?;
165 self.store.remove(&quad)?;
166 }
167 for del in &delta.deletions {
168 let quad = Self::parse_nquad(del)?;
169 self.store.insert(&quad)?;
170 }
171 return Err(GraphError::HookFailed(format!(
172 "Hook '{}' validation failed. State rolled back.",
173 hook.name
174 )));
175 }
176 }
177
178 let post_state_hash = self.state_hash()?;
179 let delta_hash = delta.hash();
180
181 Ok(TransitionReceipt::new(
182 pre_state_hash,
183 post_state_hash,
184 delta_hash,
185 ))
186 }
187}
188
189#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
191pub struct KnowledgeHook {
192 pub name: String,
194 pub sparql_query: String,
196}
197
198impl KnowledgeHook {
199 pub fn new(name: String, sparql_query: String) -> Self {
201 Self { name, sparql_query }
202 }
203
204 pub fn execute(&self, graph: &DeterministicGraph) -> Result<bool, GraphError> {
210 let results = graph.query(&self.sparql_query)?;
211 match results {
212 oxigraph::sparql::QueryResults::Boolean(val) => Ok(val),
213 oxigraph::sparql::QueryResults::Solutions(mut solutions) => {
214 let has_violations = solutions.next().is_some();
216 Ok(!has_violations)
217 }
218 oxigraph::sparql::QueryResults::Graph(_) => Err(GraphError::HookFailed(
219 "CONSTRUCT queries not supported for verification hooks".to_string(),
220 )),
221 }
222 }
223}