use crate::btree::BTree;
use crate::dictionary::NodeId;
use crate::error::Result;
use crate::index::triple::EmptyValue;
use crate::storage::BufferPool;
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 insert(&mut self, quad: Quad) -> Result<()> {
self.gspo.insert(quad.to_gspo_key(), EmptyValue)?;
self.gpos.insert(quad.to_gpos_key(), EmptyValue)?;
self.gosp.insert(quad.to_gosp_key(), EmptyValue)?;
Ok(())
}
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 query_pattern(
&self,
graph: Option<NodeId>,
subject: Option<NodeId>,
predicate: Option<NodeId>,
object: Option<NodeId>,
) -> Result<Vec<Quad>> {
let quads = if graph.is_some() && subject.is_some() {
self.query_gspo(graph, subject, predicate, object)?
} else if graph.is_some() && predicate.is_some() {
self.query_gpos(graph, predicate, object, subject)?
} else if graph.is_some() && object.is_some() {
self.query_gosp(graph, object, subject, predicate)?
} else if graph.is_some() {
self.query_gspo(graph, None, None, None)?
} else {
self.query_all_graphs(subject, predicate, object)?
};
Ok(quads)
}
fn query_gspo(
&self,
graph: Option<NodeId>,
subject: Option<NodeId>,
predicate: Option<NodeId>,
object: Option<NodeId>,
) -> Result<Vec<Quad>> {
let iter = self.gspo.range_scan(None, None)?;
let mut results = Vec::new();
for item in iter {
let (key, _value) = item?;
let quad = Quad::new(key.0, key.1, key.2, key.3);
if self.matches_pattern(&quad, graph, subject, predicate, object) {
results.push(quad);
}
}
Ok(results)
}
fn query_gpos(
&self,
graph: Option<NodeId>,
predicate: Option<NodeId>,
object: Option<NodeId>,
subject: Option<NodeId>,
) -> Result<Vec<Quad>> {
let iter = self.gpos.range_scan(None, None)?;
let mut results = Vec::new();
for item in iter {
let (key, _value) = item?;
let quad = Quad::new(key.0, key.3, key.1, key.2);
if self.matches_pattern(&quad, graph, subject, predicate, object) {
results.push(quad);
}
}
Ok(results)
}
fn query_gosp(
&self,
graph: Option<NodeId>,
object: Option<NodeId>,
subject: Option<NodeId>,
predicate: Option<NodeId>,
) -> Result<Vec<Quad>> {
let iter = self.gosp.range_scan(None, None)?;
let mut results = Vec::new();
for item in iter {
let (key, _value) = item?;
let quad = Quad::new(key.0, key.2, key.3, key.1);
if self.matches_pattern(&quad, graph, subject, predicate, object) {
results.push(quad);
}
}
Ok(results)
}
fn query_all_graphs(
&self,
subject: Option<NodeId>,
predicate: Option<NodeId>,
object: Option<NodeId>,
) -> Result<Vec<Quad>> {
self.query_gspo(None, subject, predicate, object)
}
fn matches_pattern(
&self,
quad: &Quad,
graph: Option<NodeId>,
subject: Option<NodeId>,
predicate: Option<NodeId>,
object: Option<NodeId>,
) -> bool {
if let Some(g) = graph {
if quad.graph != g {
return false;
}
}
if let Some(s) = subject {
if quad.subject != s {
return false;
}
}
if let Some(p) = predicate {
if quad.predicate != p {
return false;
}
}
if let Some(o) = object {
if quad.object != o {
return false;
}
}
true
}
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
}
}
#[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));
}
}