use crate::dictionary::{Dictionary, Term};
use crate::error::{Result, TdbError};
use crate::index::{Quad, QuadIndexes, QuadScan, Triple, TripleScan};
use crate::store::store_impl::TdbStore;
use crate::store::store_stream::decode_triple_terms;
use crate::store::store_wal::StoreOp;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum GraphName {
DefaultGraph,
Named(Term),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GraphTarget<'a> {
AnyGraph,
DefaultGraph,
Named(&'a Term),
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct QuadResult {
pub graph: GraphName,
pub subject: Term,
pub predicate: Term,
pub object: Term,
}
fn decode_quad_terms(dictionary: &Dictionary, quad: Quad) -> Result<QuadResult> {
let graph = dictionary
.decode(quad.graph)?
.ok_or_else(|| TdbError::Other("Graph id not found in dictionary".to_string()))?;
let subject = dictionary
.decode(quad.subject)?
.ok_or_else(|| TdbError::Other("Subject id not found in dictionary".to_string()))?;
let predicate = dictionary
.decode(quad.predicate)?
.ok_or_else(|| TdbError::Other("Predicate id not found in dictionary".to_string()))?;
let object = dictionary
.decode(quad.object)?
.ok_or_else(|| TdbError::Other("Object id not found in dictionary".to_string()))?;
Ok(QuadResult {
graph: GraphName::Named(graph),
subject,
predicate,
object,
})
}
pub struct QuadTermIter<'a> {
dictionary: &'a Dictionary,
default_scan: Option<TripleScan>,
named_scan: Option<QuadScan>,
}
impl<'a> QuadTermIter<'a> {
fn empty(dictionary: &'a Dictionary) -> Self {
Self {
dictionary,
default_scan: None,
named_scan: None,
}
}
}
impl Iterator for QuadTermIter<'_> {
type Item = Result<QuadResult>;
fn next(&mut self) -> Option<Self::Item> {
if let Some(scan) = self.default_scan.as_mut() {
match scan.next() {
Some(Ok(triple)) => {
return Some(decode_triple_terms(self.dictionary, triple).map(
|(subject, predicate, object)| QuadResult {
graph: GraphName::DefaultGraph,
subject,
predicate,
object,
},
));
}
Some(Err(e)) => return Some(Err(e)),
None => self.default_scan = None,
}
}
if let Some(scan) = self.named_scan.as_mut() {
match scan.next() {
Some(Ok(quad)) => return Some(decode_quad_terms(self.dictionary, quad)),
Some(Err(e)) => return Some(Err(e)),
None => self.named_scan = None,
}
}
None
}
}
impl TdbStore {
pub fn quad_count(&self) -> usize {
self.quad_count
}
pub fn dataset_len(&self) -> usize {
self.triple_count + self.quad_count
}
pub fn quads_enabled(&self) -> bool {
self.quad_indexes.is_some()
}
pub fn insert_quad(
&mut self,
graph: Option<&Term>,
subject: &Term,
predicate: &Term,
object: &Term,
) -> Result<bool> {
match graph {
None => self.insert_default_graph_quad(subject, predicate, object),
Some(graph_term) => {
if !self.quads_writable {
return Err(TdbError::Unsupported(
"named-graph writes are disabled (open with enable_quad_indexes = true)"
.to_string(),
));
}
let g_id = self.dictionary.encode(graph_term)?;
let s_id = self.dictionary.encode(subject)?;
let p_id = self.dictionary.encode(predicate)?;
let o_id = self.dictionary.encode(object)?;
let quad = Quad::new(g_id, s_id, p_id, o_id);
let is_new = {
let quad_indexes = self.quad_indexes.as_mut().ok_or_else(|| {
TdbError::Unsupported("quad indexes are not initialized".to_string())
})?;
quad_indexes.insert(quad)?
};
if is_new {
self.quad_count += 1;
self.wal_log_op(StoreOp::InsertQuad {
graph: graph_term.clone(),
subject: subject.clone(),
predicate: predicate.clone(),
object: object.clone(),
})?;
}
Ok(is_new)
}
}
}
fn insert_default_graph_quad(
&mut self,
subject: &Term,
predicate: &Term,
object: &Term,
) -> Result<bool> {
let s_id = self.dictionary.encode(subject)?;
let p_id = self.dictionary.encode(predicate)?;
let o_id = self.dictionary.encode(object)?;
let triple = Triple::new(s_id, p_id, o_id);
let is_new = self.indexes.insert(triple)?;
if let Some(ref mut bloom) = self.bloom_filter {
bloom.insert(&triple);
}
if is_new {
self.triple_count += 1;
self.wal_log_op(StoreOp::InsertTriple {
subject: subject.clone(),
predicate: predicate.clone(),
object: object.clone(),
})?;
}
self.query_cache.clear();
Ok(is_new)
}
pub fn insert_quads_bulk(
&mut self,
quads: &[(Option<Term>, Term, Term, Term)],
) -> Result<usize> {
let mut has_named = false;
for (graph, subject, _predicate, _object) in quads {
if matches!(subject, Term::Literal { .. }) {
return Err(TdbError::Other("Subject cannot be a literal".to_string()));
}
if graph.is_some() {
has_named = true;
}
}
if has_named && !self.quads_writable {
return Err(TdbError::Unsupported(
"named-graph writes are disabled (open with enable_quad_indexes = true)"
.to_string(),
));
}
if quads.is_empty() {
return Ok(0);
}
let mut encoded_triples: Vec<Triple> = Vec::new();
let mut encoded_quads: Vec<Quad> = Vec::new();
let mut ops: Vec<StoreOp> = Vec::with_capacity(quads.len());
for (graph, subject, predicate, object) in quads {
let s_id = self.dictionary.encode(subject)?;
let p_id = self.dictionary.encode(predicate)?;
let o_id = self.dictionary.encode(object)?;
match graph {
None => {
encoded_triples.push(Triple::new(s_id, p_id, o_id));
ops.push(StoreOp::InsertTriple {
subject: subject.clone(),
predicate: predicate.clone(),
object: object.clone(),
});
}
Some(graph_term) => {
let g_id = self.dictionary.encode(graph_term)?;
encoded_quads.push(Quad::new(g_id, s_id, p_id, o_id));
ops.push(StoreOp::InsertQuad {
graph: graph_term.clone(),
subject: subject.clone(),
predicate: predicate.clone(),
object: object.clone(),
});
}
}
}
let mut new_count = self.indexes.insert_sorted(&encoded_triples)?;
if let Some(ref mut bloom) = self.bloom_filter {
for triple in &encoded_triples {
bloom.insert(triple);
}
}
self.triple_count += new_count;
if !encoded_quads.is_empty() {
if self.quad_indexes.is_none() {
self.quad_indexes = Some(QuadIndexes::new(self.buffer_pool.clone()));
self.quads_writable = true;
}
let quad_indexes = self.quad_indexes.as_mut().ok_or_else(|| {
TdbError::Unsupported("quad indexes are not initialized".to_string())
})?;
let new_quads = quad_indexes.insert_sorted(&encoded_quads)?;
self.quad_count += new_quads;
new_count += new_quads;
}
self.query_cache.clear();
self.wal_log_batch(&ops)?;
self.sync()?;
Ok(new_count)
}
pub fn delete_quad(
&mut self,
graph: Option<&Term>,
subject: &Term,
predicate: &Term,
object: &Term,
) -> Result<bool> {
match graph {
None => {
let (s_id, p_id, o_id) = match self.lookup_spo(subject, predicate, object)? {
Some(ids) => ids,
None => return Ok(false),
};
let triple = Triple::new(s_id, p_id, o_id);
let deleted = self.indexes.delete(&triple)?;
if deleted {
self.triple_count = self.triple_count.saturating_sub(1);
self.query_cache.clear();
self.wal_log_op(StoreOp::DeleteTriple {
subject: subject.clone(),
predicate: predicate.clone(),
object: object.clone(),
})?;
}
Ok(deleted)
}
Some(graph_term) => {
if !self.quads_writable {
return Err(TdbError::Unsupported(
"named-graph writes are disabled (open with enable_quad_indexes = true)"
.to_string(),
));
}
let g_id = match self.dictionary.lookup(graph_term)? {
Some(id) => id,
None => return Ok(false),
};
let (s_id, p_id, o_id) = match self.lookup_spo(subject, predicate, object)? {
Some(ids) => ids,
None => return Ok(false),
};
let quad = Quad::new(g_id, s_id, p_id, o_id);
let deleted = {
let quad_indexes = self.quad_indexes.as_mut().ok_or_else(|| {
TdbError::Unsupported("quad indexes are not initialized".to_string())
})?;
quad_indexes.delete(quad)?
};
if deleted {
self.quad_count = self.quad_count.saturating_sub(1);
self.wal_log_op(StoreOp::DeleteQuad {
graph: graph_term.clone(),
subject: subject.clone(),
predicate: predicate.clone(),
object: object.clone(),
})?;
}
Ok(deleted)
}
}
}
pub fn contains_quad(
&self,
graph: Option<&Term>,
subject: &Term,
predicate: &Term,
object: &Term,
) -> Result<bool> {
match graph {
None => {
let (s_id, p_id, o_id) = match self.lookup_spo(subject, predicate, object)? {
Some(ids) => ids,
None => return Ok(false),
};
self.indexes.contains(&Triple::new(s_id, p_id, o_id))
}
Some(graph_term) => {
let g_id = match self.dictionary.lookup(graph_term)? {
Some(id) => id,
None => return Ok(false),
};
let (s_id, p_id, o_id) = match self.lookup_spo(subject, predicate, object)? {
Some(ids) => ids,
None => return Ok(false),
};
match &self.quad_indexes {
Some(quad_indexes) => quad_indexes.contains(&Quad::new(g_id, s_id, p_id, o_id)),
None => Ok(false),
}
}
}
}
fn lookup_spo(
&self,
subject: &Term,
predicate: &Term,
object: &Term,
) -> Result<
Option<(
crate::dictionary::NodeId,
crate::dictionary::NodeId,
crate::dictionary::NodeId,
)>,
> {
let s_id = match self.dictionary.lookup(subject)? {
Some(id) => id,
None => return Ok(None),
};
let p_id = match self.dictionary.lookup(predicate)? {
Some(id) => id,
None => return Ok(None),
};
let o_id = match self.dictionary.lookup(object)? {
Some(id) => id,
None => return Ok(None),
};
Ok(Some((s_id, p_id, o_id)))
}
pub fn quad_iter(
&self,
graph: GraphTarget<'_>,
subject: Option<&Term>,
predicate: Option<&Term>,
object: Option<&Term>,
) -> Result<QuadTermIter<'_>> {
let s_id = match self.resolve_pattern(subject)? {
Some(id) => id,
None => return Ok(QuadTermIter::empty(&self.dictionary)),
};
let p_id = match self.resolve_pattern(predicate)? {
Some(id) => id,
None => return Ok(QuadTermIter::empty(&self.dictionary)),
};
let o_id = match self.resolve_pattern(object)? {
Some(id) => id,
None => return Ok(QuadTermIter::empty(&self.dictionary)),
};
match graph {
GraphTarget::DefaultGraph => {
let default_scan = self.indexes.scan(s_id, p_id, o_id)?;
Ok(QuadTermIter {
dictionary: &self.dictionary,
default_scan: Some(default_scan),
named_scan: None,
})
}
GraphTarget::Named(graph_term) => {
let g_id = match self.dictionary.lookup(graph_term)? {
Some(id) => id,
None => return Ok(QuadTermIter::empty(&self.dictionary)),
};
match &self.quad_indexes {
Some(quad_indexes) => {
let named_scan = quad_indexes.scan(Some(g_id), s_id, p_id, o_id)?;
Ok(QuadTermIter {
dictionary: &self.dictionary,
default_scan: None,
named_scan: Some(named_scan),
})
}
None => Ok(QuadTermIter::empty(&self.dictionary)),
}
}
GraphTarget::AnyGraph => {
let default_scan = Some(self.indexes.scan(s_id, p_id, o_id)?);
let named_scan = match &self.quad_indexes {
Some(quad_indexes) => Some(quad_indexes.scan(None, s_id, p_id, o_id)?),
None => None,
};
Ok(QuadTermIter {
dictionary: &self.dictionary,
default_scan,
named_scan,
})
}
}
}
fn resolve_pattern(
&self,
term: Option<&Term>,
) -> Result<Option<Option<crate::dictionary::NodeId>>> {
match term {
None => Ok(Some(None)),
Some(t) => match self.dictionary.lookup(t)? {
Some(id) => Ok(Some(Some(id))),
None => Ok(None),
},
}
}
pub fn scan_quads(
&self,
graph: GraphTarget<'_>,
subject: Option<&Term>,
predicate: Option<&Term>,
object: Option<&Term>,
) -> Result<Vec<QuadResult>> {
self.quad_iter(graph, subject, predicate, object)?.collect()
}
pub fn for_each_quad<F>(
&self,
graph: GraphTarget<'_>,
subject: Option<&Term>,
predicate: Option<&Term>,
object: Option<&Term>,
mut f: F,
) -> Result<()>
where
F: FnMut(QuadResult) -> Result<()>,
{
for item in self.quad_iter(graph, subject, predicate, object)? {
f(item?)?;
}
Ok(())
}
}