use crate::store::{KgStore, StoreError, StoreResult};
use oxigraph::store::Store;
use oxigraph::io::RdfFormat;
pub struct OxigraphStore {
store: Store,
}
impl OxigraphStore {
pub fn new_memory() -> StoreResult<Self> {
let store = Store::new()
.map_err(|e| StoreError::LoadError(e.to_string()))?;
Ok(Self { store })
}
pub fn raw_query(&self, sparql: &str) -> StoreResult<oxigraph::sparql::QueryResults> {
self.store.query(sparql)
.map_err(|e| StoreError::QueryError(e.to_string()))
}
pub fn sparql_construct(&self, sparql: &str) -> StoreResult<String> {
use oxigraph::sparql::QueryResults;
match self.store.query(sparql)
.map_err(|e| StoreError::QueryError(e.to_string()))? {
QueryResults::Graph(triples) => {
let mut buf = Vec::new();
for triple in triples {
let t = triple.map_err(|e| StoreError::QueryError(e.to_string()))?;
buf.extend_from_slice(format!("{} {} {} .\n",
t.subject, t.predicate, t.object).as_bytes());
}
String::from_utf8(buf)
.map_err(|e| StoreError::QueryError(e.to_string()))
}
_ => Err(StoreError::QueryError("Expected CONSTRUCT results (Graph)".into())),
}
}
}
impl KgStore for OxigraphStore {
fn load_turtle(&mut self, turtle: &str, _graph_name: Option<&str>) -> StoreResult<usize> {
let before = self.store.len().map_err(|e| StoreError::LoadError(e.to_string()))?;
self.store
.load_from_reader(RdfFormat::Turtle, turtle.as_bytes())
.map_err(|e| StoreError::LoadError(e.to_string()))?;
let after = self.store.len().map_err(|e| StoreError::LoadError(e.to_string()))?;
Ok(after - before)
}
fn sparql_query(&self, sparql: &str) -> StoreResult<String> {
use oxigraph::sparql::QueryResults;
match self.store.query(sparql)
.map_err(|e| StoreError::QueryError(e.to_string()))? {
QueryResults::Solutions(solutions) => {
let mut rows: Vec<Vec<String>> = Vec::new();
for solution in solutions {
let s = solution.map_err(|e| StoreError::QueryError(e.to_string()))?;
let row: Vec<String> = s.values()
.iter()
.map(|v| match v {
Some(term) => term.to_string(),
None => String::new(),
})
.collect();
rows.push(row);
}
serde_json::to_string(&rows)
.map_err(|e| StoreError::QueryError(e.to_string()))
}
QueryResults::Boolean(b) => Ok(b.to_string()),
QueryResults::Graph(_) => {
Err(StoreError::QueryError("CONSTRUCT: use export_turtle instead".into()))
}
}
}
fn sparql_update(&mut self, sparql: &str) -> StoreResult<()> {
self.store
.update(sparql)
.map_err(|e| StoreError::QueryError(e.to_string()))
}
fn export_turtle(&self, _graph_name: Option<&str>) -> StoreResult<String> {
let mut buf = Vec::new();
self.store
.dump_to_writer(RdfFormat::Turtle, &mut buf)
.map_err(|e| StoreError::ExportError(e.to_string()))?;
String::from_utf8(buf)
.map_err(|e| StoreError::ExportError(e.to_string()))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn load_and_query() {
let mut store = OxigraphStore::new_memory().unwrap();
let ttl = r#"
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
<http://example.org/A> a rdfs:Class ;
rdfs:label "Test"@en .
"#;
let count = store.load_turtle(ttl, None).unwrap();
assert_eq!(count, 2);
let result = store.sparql_query(
"SELECT ?s WHERE { ?s a <http://www.w3.org/2000/01/rdf-schema#Class> }"
).unwrap();
assert!(result.contains("example.org/A"));
}
}