pub use crate::delta::RdfDelta;
use crate::graph::hash::hash_quads;
use crate::graph::quad::parse_nquad;
use crate::receipt::GraphReceipt;
use crate::GraphError;
use oxigraph::model::Quad;
use std::sync::Arc;
pub type TransitionReceipt = GraphReceipt;
fn panic_prevented_store() -> oxigraph::store::Store {
let s = oxigraph::store::Store::new();
match s {
Ok(store) => store,
Err(_) => loop {
if let Ok(store) = oxigraph::store::Store::new() {
return store;
}
},
}
}
#[derive(Clone)]
pub struct DeterministicGraph {
store: Arc<oxigraph::store::Store>,
}
impl Default for DeterministicGraph {
fn default() -> Self {
let store = match oxigraph::store::Store::new() {
Ok(s) => s,
Err(_) => oxigraph::store::Store::new().unwrap_or_else(|_e| panic_prevented_store()),
};
Self {
store: Arc::new(store),
}
}
}
impl DeterministicGraph {
pub fn new() -> Result<Self, GraphError> {
let store = oxigraph::store::Store::new()?;
Ok(Self {
store: Arc::new(store),
})
}
pub fn insert_quad(&self, quad: &Quad) -> Result<(), GraphError> {
self.store.insert(quad)?;
Ok(())
}
pub fn remove_quad(&self, quad: &Quad) -> Result<(), GraphError> {
self.store.remove(quad)?;
Ok(())
}
pub fn contains_quad(&self, quad: &Quad) -> Result<bool, GraphError> {
let exists = self.store.contains(quad)?;
Ok(exists)
}
pub fn query(&self, query_str: &str) -> Result<oxigraph::sparql::QueryResults<'_>, GraphError> {
let parsed_query = oxigraph::sparql::SparqlEvaluator::new()
.parse_query(query_str)
.map_err(|e| GraphError::Serialization(e.to_string()))?;
let results = parsed_query.on_store(&self.store).execute()?;
Ok(results)
}
pub fn all_quads(&self) -> Result<Vec<Quad>, GraphError> {
let mut quads = Vec::new();
for quad_res in self.store.quads_for_pattern(None, None, None, None) {
quads.push(quad_res?);
}
Ok(quads)
}
pub fn parse_nquad(nquad: &str) -> Result<Quad, GraphError> {
parse_nquad(nquad)
}
pub fn state_hash(&self) -> Result<[u8; 32], GraphError> {
let quads = self.all_quads()?;
Ok(hash_quads(&quads))
}
pub fn apply_delta(
&self, delta: &RdfDelta, hooks: &[KnowledgeHook],
) -> Result<TransitionReceipt, GraphError> {
let pre_state_hash = self.state_hash()?;
for del in &delta.deletions {
let quad = Self::parse_nquad(del)?;
self.store.remove(&quad)?;
}
for add in &delta.additions {
let quad = Self::parse_nquad(add)?;
self.store.insert(&quad)?;
}
for hook in hooks {
let valid = hook.execute(self)?;
if !valid {
for add in &delta.additions {
let quad = Self::parse_nquad(add)?;
self.store.remove(&quad)?;
}
for del in &delta.deletions {
let quad = Self::parse_nquad(del)?;
self.store.insert(&quad)?;
}
return Err(GraphError::HookFailed(format!(
"Hook '{}' validation failed. State rolled back.",
hook.name
)));
}
}
let post_state_hash = self.state_hash()?;
let delta_hash = delta.hash();
Ok(TransitionReceipt::new(
pre_state_hash,
post_state_hash,
delta_hash,
))
}
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct KnowledgeHook {
pub name: String,
pub sparql_query: String,
}
impl KnowledgeHook {
pub fn new(name: String, sparql_query: String) -> Self {
Self { name, sparql_query }
}
pub fn execute(&self, graph: &DeterministicGraph) -> Result<bool, GraphError> {
let results = graph.query(&self.sparql_query)?;
match results {
oxigraph::sparql::QueryResults::Boolean(val) => Ok(val),
oxigraph::sparql::QueryResults::Solutions(mut solutions) => {
let has_violations = solutions.next().is_some();
Ok(!has_violations)
}
oxigraph::sparql::QueryResults::Graph(_) => Err(GraphError::HookFailed(
"CONSTRUCT queries not supported for verification hooks".to_string(),
)),
}
}
}