use std::path::Path;
use fjall::{Config, Keyspace, Partition, PartitionCreateOptions, PersistMode};
use oxigraph::io::{RdfFormat, RdfParser};
use oxigraph::model::Quad;
use oxigraph::store::Store;
use crate::{Error, Result};
const QUADS_PARTITION: &str = "oxiland_quads";
#[derive(Clone)]
pub(crate) struct DiskStore {
keyspace: Keyspace,
quads: Partition,
}
impl DiskStore {
pub(crate) fn open(path: &Path) -> Result<Self> {
std::fs::create_dir_all(path).map_err(|error| Error::OpenStore {
path: path.to_owned(),
message: error.to_string(),
})?;
let keyspace = Config::new(path).open().map_err(|error| Error::OpenStore {
path: path.to_owned(),
message: error.to_string(),
})?;
let quads = keyspace
.open_partition(QUADS_PARTITION, PartitionCreateOptions::default())
.map_err(|error| Error::OpenStore {
path: path.to_owned(),
message: error.to_string(),
})?;
Ok(Self { keyspace, quads })
}
pub(crate) fn load_into(&self, store: &Store) -> Result<()> {
for entry in self.quads.iter() {
let (key, _) = entry.map_err(|error| Error::Storage(error.to_string()))?;
let key = std::str::from_utf8(&key).map_err(|error| {
Error::Storage(format!("persisted quad key was not UTF-8: {error}"))
})?;
let quad = parse_quad(key)?;
store
.insert(&quad)
.map_err(|error| Error::Storage(error.to_string()))?;
}
Ok(())
}
pub(crate) fn insert(&self, quad: &Quad) -> Result<()> {
let key = quad_key(quad);
self.quads
.insert(key.as_bytes(), [])
.map_err(|error| Error::Storage(error.to_string()))?;
self.keyspace
.persist(PersistMode::SyncAll)
.map_err(|error| Error::Storage(error.to_string()))
}
pub(crate) fn remove(&self, quad: &Quad) -> Result<()> {
let key = quad_key(quad);
self.quads
.remove(key.as_bytes())
.map_err(|error| Error::Storage(error.to_string()))?;
self.keyspace
.persist(PersistMode::SyncAll)
.map_err(|error| Error::Storage(error.to_string()))
}
}
fn quad_key(quad: &Quad) -> String {
format!("{quad} .")
}
fn parse_quad(key: &str) -> Result<Quad> {
let mut parsed = RdfParser::from_format(RdfFormat::NQuads).for_reader(key.as_bytes());
let quad = parsed
.next()
.ok_or_else(|| Error::Storage("persisted quad key was empty".into()))?
.map_err(|error| Error::Storage(error.to_string()))?;
if parsed.next().is_some() {
return Err(Error::Storage(
"persisted quad key contained multiple quads".into(),
));
}
Ok(quad)
}