use crate::btree::iterator::BTreeIterator;
use crate::btree::BTree;
use crate::dictionary::NodeId;
use crate::error::Result;
use crate::index::triple::{prefix_bounds, EmptyValue};
use crate::storage::{BufferPool, PageId};
use serde::{Deserialize, Serialize};
use std::sync::Arc;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct Quad {
pub graph: NodeId,
pub subject: NodeId,
pub predicate: NodeId,
pub object: NodeId,
}
impl Quad {
pub fn new(graph: NodeId, subject: NodeId, predicate: NodeId, object: NodeId) -> Self {
Self {
graph,
subject,
predicate,
object,
}
}
pub fn to_gspo_key(&self) -> GspoKey {
GspoKey(self.graph, self.subject, self.predicate, self.object)
}
pub fn to_gpos_key(&self) -> GposKey {
GposKey(self.graph, self.predicate, self.object, self.subject)
}
pub fn to_gosp_key(&self) -> GospKey {
GospKey(self.graph, self.object, self.subject, self.predicate)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct GspoKey(pub NodeId, pub NodeId, pub NodeId, pub NodeId);
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct GposKey(pub NodeId, pub NodeId, pub NodeId, pub NodeId);
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct GospKey(pub NodeId, pub NodeId, pub NodeId, pub NodeId);
pub struct QuadIndexes {
gspo: BTree<GspoKey, EmptyValue>,
gpos: BTree<GposKey, EmptyValue>,
gosp: BTree<GospKey, EmptyValue>,
buffer_pool: Arc<BufferPool>,
}
impl QuadIndexes {
pub fn new(buffer_pool: Arc<BufferPool>) -> Self {
Self {
gspo: BTree::new(buffer_pool.clone()),
gpos: BTree::new(buffer_pool.clone()),
gosp: BTree::new(buffer_pool.clone()),
buffer_pool,
}
}
pub fn from_roots(
buffer_pool: Arc<BufferPool>,
gspo_root: Option<PageId>,
gpos_root: Option<PageId>,
gosp_root: Option<PageId>,
) -> Self {
let gspo = match gspo_root {
Some(page) => BTree::from_root(buffer_pool.clone(), page),
None => BTree::new(buffer_pool.clone()),
};
let gpos = match gpos_root {
Some(page) => BTree::from_root(buffer_pool.clone(), page),
None => BTree::new(buffer_pool.clone()),
};
let gosp = match gosp_root {
Some(page) => BTree::from_root(buffer_pool.clone(), page),
None => BTree::new(buffer_pool.clone()),
};
Self {
gspo,
gpos,
gosp,
buffer_pool,
}
}
pub fn gspo_root(&self) -> Option<PageId> {
self.gspo.root_page()
}
pub fn gpos_root(&self) -> Option<PageId> {
self.gpos.root_page()
}
pub fn gosp_root(&self) -> Option<PageId> {
self.gosp.root_page()
}
pub fn insert(&mut self, quad: Quad) -> Result<bool> {
let is_new = self.gspo.insert(quad.to_gspo_key(), EmptyValue)?.is_none();
self.gpos.insert(quad.to_gpos_key(), EmptyValue)?;
self.gosp.insert(quad.to_gosp_key(), EmptyValue)?;
Ok(is_new)
}
pub fn insert_sorted(&mut self, quads: &[Quad]) -> Result<usize> {
let mut gspo_keys: Vec<GspoKey> = quads.iter().map(|q| q.to_gspo_key()).collect();
gspo_keys.sort_unstable();
let mut new_count = 0usize;
for key in gspo_keys {
if self.gspo.insert(key, EmptyValue)?.is_none() {
new_count += 1;
}
}
let mut gpos_keys: Vec<GposKey> = quads.iter().map(|q| q.to_gpos_key()).collect();
gpos_keys.sort_unstable();
for key in gpos_keys {
self.gpos.insert(key, EmptyValue)?;
}
let mut gosp_keys: Vec<GospKey> = quads.iter().map(|q| q.to_gosp_key()).collect();
gosp_keys.sort_unstable();
for key in gosp_keys {
self.gosp.insert(key, EmptyValue)?;
}
Ok(new_count)
}
pub fn delete(&mut self, quad: Quad) -> Result<bool> {
let gspo_deleted = self.gspo.delete(&quad.to_gspo_key())?.is_some();
let gpos_deleted = self.gpos.delete(&quad.to_gpos_key())?.is_some();
let gosp_deleted = self.gosp.delete(&quad.to_gosp_key())?.is_some();
Ok(gspo_deleted && gpos_deleted && gosp_deleted)
}
pub fn contains(&self, quad: &Quad) -> Result<bool> {
Ok(self.gspo.search(&quad.to_gspo_key())?.is_some())
}
pub fn scan(
&self,
graph: Option<NodeId>,
subject: Option<NodeId>,
predicate: Option<NodeId>,
object: Option<NodeId>,
) -> Result<QuadScan> {
let inner = if subject.is_some() {
let (start, end) = prefix_bounds([graph, subject, predicate, object]);
QuadScanInner::Gspo(self.gspo.range_scan(
start.map(|k| GspoKey(k[0], k[1], k[2], k[3])),
end.map(|k| GspoKey(k[0], k[1], k[2], k[3])),
)?)
} else if predicate.is_some() {
let (start, end) = prefix_bounds([graph, predicate, object, subject]);
QuadScanInner::Gpos(self.gpos.range_scan(
start.map(|k| GposKey(k[0], k[1], k[2], k[3])),
end.map(|k| GposKey(k[0], k[1], k[2], k[3])),
)?)
} else if object.is_some() {
let (start, end) = prefix_bounds([graph, object, subject, predicate]);
QuadScanInner::Gosp(self.gosp.range_scan(
start.map(|k| GospKey(k[0], k[1], k[2], k[3])),
end.map(|k| GospKey(k[0], k[1], k[2], k[3])),
)?)
} else {
let (start, end) = prefix_bounds([graph, None, None, None]);
QuadScanInner::Gspo(self.gspo.range_scan(
start.map(|k| GspoKey(k[0], k[1], k[2], k[3])),
end.map(|k| GspoKey(k[0], k[1], k[2], k[3])),
)?)
};
Ok(QuadScan {
inner,
graph,
subject,
predicate,
object,
})
}
pub fn query_pattern(
&self,
graph: Option<NodeId>,
subject: Option<NodeId>,
predicate: Option<NodeId>,
object: Option<NodeId>,
) -> Result<Vec<Quad>> {
self.scan(graph, subject, predicate, object)?.collect()
}
pub fn gspo(&self) -> &BTree<GspoKey, EmptyValue> {
&self.gspo
}
pub fn gpos(&self) -> &BTree<GposKey, EmptyValue> {
&self.gpos
}
pub fn gosp(&self) -> &BTree<GospKey, EmptyValue> {
&self.gosp
}
}
enum QuadScanInner {
Gspo(BTreeIterator<GspoKey, EmptyValue>),
Gpos(BTreeIterator<GposKey, EmptyValue>),
Gosp(BTreeIterator<GospKey, EmptyValue>),
}
pub struct QuadScan {
inner: QuadScanInner,
graph: Option<NodeId>,
subject: Option<NodeId>,
predicate: Option<NodeId>,
object: Option<NodeId>,
}
impl QuadScan {
fn matches(&self, quad: &Quad) -> bool {
self.graph.map_or(true, |g| quad.graph == g)
&& self.subject.map_or(true, |s| quad.subject == s)
&& self.predicate.map_or(true, |p| quad.predicate == p)
&& self.object.map_or(true, |o| quad.object == o)
}
}
impl Iterator for QuadScan {
type Item = Result<Quad>;
fn next(&mut self) -> Option<Self::Item> {
loop {
let next = match &mut self.inner {
QuadScanInner::Gspo(it) => it
.next()
.map(|r| r.map(|(k, _)| Quad::new(k.0, k.1, k.2, k.3))),
QuadScanInner::Gpos(it) => it
.next()
.map(|r| r.map(|(k, _)| Quad::new(k.0, k.3, k.1, k.2))),
QuadScanInner::Gosp(it) => it
.next()
.map(|r| r.map(|(k, _)| Quad::new(k.0, k.2, k.3, k.1))),
};
match next {
None => return None,
Some(Err(e)) => return Some(Err(e)),
Some(Ok(quad)) => {
if self.matches(&quad) {
return Some(Ok(quad));
}
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::storage::FileManager;
use std::env;
use tempfile::TempDir;
fn setup_indexes() -> QuadIndexes {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("test.db");
let file_manager = Arc::new(FileManager::open(&db_path, true).unwrap());
let buffer_pool = Arc::new(BufferPool::new(100, file_manager));
QuadIndexes::new(buffer_pool)
}
#[test]
fn test_quad_creation() {
let quad = Quad::new(
NodeId::from(0),
NodeId::from(1),
NodeId::from(2),
NodeId::from(3),
);
assert_eq!(quad.graph, NodeId::from(0));
assert_eq!(quad.subject, NodeId::from(1));
assert_eq!(quad.predicate, NodeId::from(2));
assert_eq!(quad.object, NodeId::from(3));
}
#[test]
fn test_quad_indexes_creation() {
let _indexes = setup_indexes();
}
#[test]
fn test_quad_insert() {
let mut indexes = setup_indexes();
let quad = Quad::new(
NodeId::from(0),
NodeId::from(1),
NodeId::from(2),
NodeId::from(3),
);
indexes.insert(quad).unwrap();
assert!(indexes.contains(&quad).unwrap());
}
#[test]
fn test_quad_delete() {
let mut indexes = setup_indexes();
let quad = Quad::new(
NodeId::from(0),
NodeId::from(1),
NodeId::from(2),
NodeId::from(3),
);
indexes.insert(quad).unwrap();
assert!(indexes.contains(&quad).unwrap());
let deleted = indexes.delete(quad).unwrap();
assert!(deleted);
assert!(!indexes.contains(&quad).unwrap());
}
#[test]
fn test_quad_query_by_graph() {
let mut indexes = setup_indexes();
indexes
.insert(Quad::new(
NodeId::from(0),
NodeId::from(1),
NodeId::from(2),
NodeId::from(3),
))
.unwrap();
indexes
.insert(Quad::new(
NodeId::from(0),
NodeId::from(4),
NodeId::from(5),
NodeId::from(6),
))
.unwrap();
indexes
.insert(Quad::new(
NodeId::from(1),
NodeId::from(7),
NodeId::from(8),
NodeId::from(9),
))
.unwrap();
let results = indexes
.query_pattern(Some(NodeId::from(0)), None, None, None)
.unwrap();
assert_eq!(results.len(), 2);
for quad in &results {
assert_eq!(quad.graph, NodeId::from(0));
}
}
#[test]
fn test_quad_query_full_pattern() {
let mut indexes = setup_indexes();
let quad = Quad::new(
NodeId::from(0),
NodeId::from(1),
NodeId::from(2),
NodeId::from(3),
);
indexes.insert(quad).unwrap();
let results = indexes
.query_pattern(
Some(NodeId::from(0)),
Some(NodeId::from(1)),
Some(NodeId::from(2)),
Some(NodeId::from(3)),
)
.unwrap();
assert_eq!(results.len(), 1);
assert_eq!(results[0], quad);
}
#[test]
fn test_quad_query_by_predicate() {
let mut indexes = setup_indexes();
indexes
.insert(Quad::new(
NodeId::from(0),
NodeId::from(1),
NodeId::from(2),
NodeId::from(3),
))
.unwrap();
indexes
.insert(Quad::new(
NodeId::from(0),
NodeId::from(4),
NodeId::from(2),
NodeId::from(6),
))
.unwrap();
indexes
.insert(Quad::new(
NodeId::from(0),
NodeId::from(7),
NodeId::from(8),
NodeId::from(9),
))
.unwrap();
let results = indexes
.query_pattern(Some(NodeId::from(0)), None, Some(NodeId::from(2)), None)
.unwrap();
assert_eq!(results.len(), 2);
for quad in &results {
assert_eq!(quad.graph, NodeId::from(0));
assert_eq!(quad.predicate, NodeId::from(2));
}
}
#[test]
fn test_quad_key_conversions() {
let quad = Quad::new(
NodeId::from(0),
NodeId::from(1),
NodeId::from(2),
NodeId::from(3),
);
let gspo = quad.to_gspo_key();
assert_eq!(gspo.0, NodeId::from(0));
assert_eq!(gspo.1, NodeId::from(1));
assert_eq!(gspo.2, NodeId::from(2));
assert_eq!(gspo.3, NodeId::from(3));
let gpos = quad.to_gpos_key();
assert_eq!(gpos.0, NodeId::from(0));
assert_eq!(gpos.1, NodeId::from(2));
assert_eq!(gpos.2, NodeId::from(3));
assert_eq!(gpos.3, NodeId::from(1));
let gosp = quad.to_gosp_key();
assert_eq!(gosp.0, NodeId::from(0));
assert_eq!(gosp.1, NodeId::from(3));
assert_eq!(gosp.2, NodeId::from(1));
assert_eq!(gosp.3, NodeId::from(2));
}
}