use crate::{GraphStore, NodeId, Predicate, Result, Triple, Value};
#[derive(Debug, Clone, Default)]
pub struct TriplePattern {
pub subject: Option<NodeId>,
pub predicate: Option<Predicate>,
pub object: Option<Value>,
}
impl TriplePattern {
pub fn any() -> Self {
Self::default()
}
pub fn subject(subject: NodeId) -> Self {
Self {
subject: Some(subject),
..Default::default()
}
}
pub fn predicate(predicate: Predicate) -> Self {
Self {
predicate: Some(predicate),
..Default::default()
}
}
pub fn object(object: Value) -> Self {
Self {
object: Some(object),
..Default::default()
}
}
pub fn with_subject(mut self, subject: NodeId) -> Self {
self.subject = Some(subject);
self
}
pub fn with_predicate(mut self, predicate: Predicate) -> Self {
self.predicate = Some(predicate);
self
}
pub fn with_object(mut self, object: Value) -> Self {
self.object = Some(object);
self
}
pub fn matches(&self, triple: &Triple) -> bool {
if let Some(ref s) = self.subject {
if &triple.subject != s {
return false;
}
}
if let Some(ref p) = self.predicate {
if &triple.predicate != p {
return false;
}
}
if let Some(ref o) = self.object {
if &triple.object != o {
return false;
}
}
true
}
pub fn is_exact(&self) -> bool {
self.subject.is_some() && self.predicate.is_some() && self.object.is_some()
}
pub fn is_wildcard(&self) -> bool {
self.subject.is_none() && self.predicate.is_none() && self.object.is_none()
}
}
#[derive(Debug, Clone)]
pub struct QueryResult {
pub triples: Vec<Triple>,
pub total_count: usize,
pub has_more: bool,
}
impl QueryResult {
pub fn new(triples: Vec<Triple>) -> Self {
let total_count = triples.len();
Self {
triples,
total_count,
has_more: false,
}
}
pub fn first(&self) -> Option<&Triple> {
self.triples.first()
}
pub fn is_empty(&self) -> bool {
self.triples.is_empty()
}
pub fn len(&self) -> usize {
self.triples.len()
}
}
pub struct QueryBuilder<'a> {
store: &'a GraphStore,
pattern: TriplePattern,
limit: Option<usize>,
offset: usize,
}
impl<'a> QueryBuilder<'a> {
pub fn new(store: &'a GraphStore) -> Self {
Self {
store,
pattern: TriplePattern::default(),
limit: None,
offset: 0,
}
}
pub fn subject(mut self, subject: NodeId) -> Self {
self.pattern.subject = Some(subject);
self
}
pub fn predicate(mut self, predicate: Predicate) -> Self {
self.pattern.predicate = Some(predicate);
self
}
pub fn object(mut self, object: Value) -> Self {
self.pattern.object = Some(object);
self
}
pub fn limit(mut self, limit: usize) -> Self {
self.limit = Some(limit);
self
}
pub fn offset(mut self, offset: usize) -> Self {
self.offset = offset;
self
}
pub fn execute(self) -> Result<QueryResult> {
let mut triples = self.store.find(self.pattern)?;
let total_count = triples.len();
if self.offset > 0 {
if self.offset >= triples.len() {
triples.clear();
} else {
triples = triples.into_iter().skip(self.offset).collect();
}
}
let has_more = if let Some(limit) = self.limit {
let exceeded = triples.len() > limit;
triples.truncate(limit);
exceeded
} else {
false
};
Ok(QueryResult {
triples,
total_count,
has_more,
})
}
}
pub struct TraversalBuilder<'a> {
store: &'a GraphStore,
start: NodeId,
predicates: Vec<Predicate>,
max_depth: usize,
follow_inverse: bool,
}
impl<'a> TraversalBuilder<'a> {
pub fn from(store: &'a GraphStore, start: NodeId) -> Self {
Self {
store,
start,
predicates: Vec::new(),
max_depth: 10,
follow_inverse: false,
}
}
pub fn follow(mut self, predicate: Predicate) -> Self {
self.predicates.push(predicate);
self
}
pub fn follow_all(mut self, predicates: Vec<Predicate>) -> Self {
self.predicates.extend(predicates);
self
}
pub fn max_depth(mut self, depth: usize) -> Self {
self.max_depth = depth;
self
}
pub fn bidirectional(mut self) -> Self {
self.follow_inverse = true;
self
}
pub fn execute(self) -> Result<Vec<NodeId>> {
self.store.traverse(&self.start, &self.predicates)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_pattern_matches() {
let triple = Triple::new(
NodeId::named("user:alice"),
Predicate::named("has_name"),
Value::literal("Alice"),
);
assert!(TriplePattern::any().matches(&triple));
assert!(TriplePattern::subject(NodeId::named("user:alice")).matches(&triple));
assert!(!TriplePattern::subject(NodeId::named("user:bob")).matches(&triple));
assert!(TriplePattern::predicate(Predicate::named("has_name")).matches(&triple));
assert!(!TriplePattern::predicate(Predicate::named("has_age")).matches(&triple));
assert!(TriplePattern::object(Value::literal("Alice")).matches(&triple));
assert!(!TriplePattern::object(Value::literal("Bob")).matches(&triple));
let pattern = TriplePattern::subject(NodeId::named("user:alice"))
.with_predicate(Predicate::named("has_name"));
assert!(pattern.matches(&triple));
}
#[test]
fn test_pattern_is_exact() {
let partial = TriplePattern::subject(NodeId::named("a"));
assert!(!partial.is_exact());
let exact = TriplePattern::subject(NodeId::named("a"))
.with_predicate(Predicate::named("b"))
.with_object(Value::literal("c"));
assert!(exact.is_exact());
}
#[test]
fn test_query_result() {
let t1 = Triple::new(
NodeId::named("a"),
Predicate::named("p"),
Value::literal("b"),
);
let t2 = Triple::new(
NodeId::named("c"),
Predicate::named("p"),
Value::literal("d"),
);
let result = QueryResult::new(vec![t1.clone(), t2]);
assert_eq!(result.len(), 2);
assert!(!result.is_empty());
assert_eq!(result.first().unwrap().subject, t1.subject);
}
}