diff --git a/lib/rdf-borsh/src/borsh_reader.rs b/lib/rdf-borsh/src/borsh_reader.rs index 6186d74..f700c8d 100644 --- a/lib/rdf-borsh/src/borsh_reader.rs +++ b/lib/rdf-borsh/src/borsh_reader.rs @@ -69,7 +69,15 @@ impl Reader for BorshReader { } impl Source for BorshReader {} -impl Enumerable for BorshReader {} + +impl Enumerable for BorshReader { + fn grep(&self, pattern: &impl PartialEq>) -> Self + where + Self: Sized, + { + todo!() + } +} impl MaybeDurable for BorshReader {} impl MaybeIndexed for BorshReader {} impl MaybeMutable for BorshReader {} diff --git a/lib/rdf-model/src/lib.rs b/lib/rdf-model/src/lib.rs index 74c82c0..5941d12 100644 --- a/lib/rdf-model/src/lib.rs +++ b/lib/rdf-model/src/lib.rs @@ -69,9 +69,6 @@ mod traits { mod maybe_mutable; pub use maybe_mutable::*; - - mod queryable; - pub use queryable::*; } pub use traits::*; diff --git a/lib/rdf-model/src/traits/enumerable.rs b/lib/rdf-model/src/traits/enumerable.rs index 2c10648..2577eef 100644 --- a/lib/rdf-model/src/traits/enumerable.rs +++ b/lib/rdf-model/src/traits/enumerable.rs @@ -10,4 +10,7 @@ use core::error::Error; pub trait Enumerable: Countable + Iterator, Box>> { + fn grep(&self, pattern: &impl PartialEq>) -> Self + where + Self: Sized; } diff --git a/lib/rdf-model/src/traits/queryable.rs b/lib/rdf-model/src/traits/queryable.rs deleted file mode 100644 index c9bd216..0000000 --- a/lib/rdf-model/src/traits/queryable.rs +++ /dev/null @@ -1,5 +0,0 @@ -// This is free and unencumbered software released into the public domain. - -use super::Enumerable; - -pub trait Queryable: Enumerable {} diff --git a/lib/rdf-query/src/graph_pattern.rs b/lib/rdf-query/src/graph_pattern.rs new file mode 100644 index 0000000..acd644f --- /dev/null +++ b/lib/rdf-query/src/graph_pattern.rs @@ -0,0 +1,18 @@ +use crate::{pattern::Pattern, query::Query}; + +pub enum GraphPattern { + BasicGraphPattern(Query), + TriplePattern(Pattern), +} + +impl From for GraphPattern { + fn from(query: Query) -> Self { + Self::BasicGraphPattern(query) + } +} + +impl From for GraphPattern { + fn from(pattern: Pattern) -> Self { + Self::TriplePattern(pattern) + } +} diff --git a/lib/rdf-query/src/lib.rs b/lib/rdf-query/src/lib.rs index d8284e4..2d8890d 100644 --- a/lib/rdf-query/src/lib.rs +++ b/lib/rdf-query/src/lib.rs @@ -6,3 +6,15 @@ #![no_std] #![deny(unsafe_code)] + +pub mod graph_pattern; +pub mod matcher; +pub mod pattern; +pub mod query; +pub mod solution; +pub mod solutions; +pub mod variable; + +mod traits { + pub mod queryable; +} diff --git a/lib/rdf-query/src/matcher.rs b/lib/rdf-query/src/matcher.rs new file mode 100644 index 0000000..537c07a --- /dev/null +++ b/lib/rdf-query/src/matcher.rs @@ -0,0 +1,56 @@ +extern crate alloc; + +use alloc::boxed::Box; +use rdf_model::Term; + +use crate::variable::Variable; + +pub enum Matcher { + Variable(Variable), + Term(Box), +} + +impl Matcher { + pub fn as_variable(&self) -> Option<&Variable> { + match self { + Self::Variable(var) => Some(var), + _ => None, + } + } +} + +impl PartialEq<&dyn Term> for Matcher { + fn eq(&self, term: &&dyn Term) -> bool { + match self { + Self::Variable(_) => true, + Self::Term(t) => t.as_str() == term.as_str(), + } + } +} + +impl From for Matcher { + fn from(var: Variable) -> Self { + Self::Variable(var) + } +} + +impl From<&str> for Matcher { + fn from(name: &str) -> Self { + name.into() + } +} + +impl From> for Matcher { + fn from(term: Box) -> Self { + Self::Term(term) + } +} + +impl core::fmt::Debug for Matcher { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + match self { + Self::Variable(var) => write!(f, "{:?}", var), + Self::Term(t) => f.write_str(&t.as_str()), + } + } +} diff --git a/lib/rdf-query/src/pattern.rs b/lib/rdf-query/src/pattern.rs new file mode 100644 index 0000000..de44559 --- /dev/null +++ b/lib/rdf-query/src/pattern.rs @@ -0,0 +1,83 @@ +extern crate alloc; + +use alloc::{boxed::Box, collections::BTreeMap, vec}; +use rdf_model::{Statement, Term}; + +use crate::{ + matcher::Matcher, solution::Solution, traits::queryable::Queryable, variable::Variable, +}; + +pub struct Pattern { + subject: Matcher, + predicate: Matcher, + object: Matcher, + graph_name: Option>, +} + +impl Pattern { + pub fn new( + subject: impl Into, + predicate: impl Into, + object: impl Into, + graph_name: Option>, + ) -> Self { + Self { + subject: subject.into(), + predicate: predicate.into(), + object: object.into(), + graph_name, + } + } + + pub(crate) fn execute(&self, queryable: &Q) -> Q + where + Self: Sized, + { + queryable.query_pattern(self) + } + + /// Returns a query solution constructed by binding any variables in this + /// pattern with the corresponding terms in the given statement. + pub fn solution(&self, statement: Box) -> Solution { + let mut ans = BTreeMap::new(); + + let bindings = vec![ + (self.subject.as_variable(), statement.subject()), + (self.predicate.as_variable(), statement.predicate()), + (self.object.as_variable(), statement.object()), + ]; + + for (variable, term) in bindings { + if let Some(variable) = variable.map(Variable::name).map(Variable::unbound) { + ans.insert(variable, term); + } + } + + Solution::new(ans) + } +} + +impl PartialEq> for Pattern { + fn eq(&self, statement: &Box) -> bool { + let (s, p, o) = ( + statement.subject(), + statement.predicate(), + statement.object(), + ); + self.subject == s && self.predicate == p && self.object == o + } +} + +impl core::fmt::Debug for Pattern { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + f.debug_struct("Pattern") + .field("subject", &self.subject) + .field("predicate", &self.predicate) + .field("object", &self.object) + .field( + "graph_name", + &self.graph_name.as_ref().map(|gn| gn.as_str()), + ) + .finish() + } +} diff --git a/lib/rdf-query/src/query.rs b/lib/rdf-query/src/query.rs new file mode 100644 index 0000000..75af92d --- /dev/null +++ b/lib/rdf-query/src/query.rs @@ -0,0 +1,41 @@ +extern crate alloc; + +use alloc::vec::Vec; + +use crate::{pattern::Pattern, solutions::Solutions, traits::queryable::Queryable}; + +pub struct Query { + patterns: Vec, +} + +impl Query { + pub fn new(patterns: Vec) -> Self { + Self { patterns } + } + + /// Executes the query on the given graph. + /// + /// If the query nas no patterns, it returns a single empty solution as per + /// SPARQL 1.1 Empty Group Pattern. + pub fn execute(&self, queryable: &Q) -> Solutions { + if self.empty() { + return Solutions::empty(); + } + + let solutions: Vec<_> = self + .patterns + .iter() + .flat_map(|pattern| { + let statements = pattern.execute(queryable); + statements.filter_map(|res| res.ok().map(|stmt| pattern.solution(stmt))) + }) + .collect(); + + Solutions::new(solutions.into_iter()) + } + + /// Returns true if the query has no patterns. + pub fn empty(&self) -> bool { + self.patterns.is_empty() + } +} diff --git a/lib/rdf-query/src/solution.rs b/lib/rdf-query/src/solution.rs new file mode 100644 index 0000000..7d0d0eb --- /dev/null +++ b/lib/rdf-query/src/solution.rs @@ -0,0 +1,43 @@ +extern crate alloc; + +use alloc::collections::BTreeMap; +use rdf_model::HeapTerm; + +use crate::variable::Variable; + +pub struct Solution { + bindings: BTreeMap, +} + +impl Solution { + pub fn new(bindings: BTreeMap>) -> Self { + let bindings = bindings + .into_iter() + .map(|(var, term)| (var, term.into())) + .collect(); + + Self { bindings } + } + + pub fn binding(&self, var: &Variable) -> Option<&HeapTerm> { + self.bindings.get(var) + } + + pub fn each_binding(&self) -> impl Iterator { + self.bindings.iter() + } + + pub fn each_name(&self) -> impl Iterator { + self.bindings.keys() + } + + pub fn each_value(&self) -> impl Iterator { + self.bindings.values() + } +} + +impl core::fmt::Debug for Solution { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + f.debug_struct("Solution").finish() + } +} diff --git a/lib/rdf-query/src/solutions.rs b/lib/rdf-query/src/solutions.rs new file mode 100644 index 0000000..d50a86c --- /dev/null +++ b/lib/rdf-query/src/solutions.rs @@ -0,0 +1,37 @@ +extern crate alloc; + +use alloc::boxed::Box; + +use crate::solution::Solution; + +pub struct Solutions { + iter: Box>, +} + +impl Solutions { + pub fn new(iter: impl Iterator + 'static) -> Self { + Self { + iter: Box::new(iter), + } + } + + pub fn empty() -> Self { + Self { + iter: Box::new(core::iter::empty()), + } + } +} + +impl Iterator for Solutions { + type Item = Solution; + + fn next(&mut self) -> Option { + self.iter.next() + } +} + +impl core::fmt::Debug for Solutions { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + f.debug_struct("Solutions").finish() + } +} diff --git a/lib/rdf-query/src/traits/queryable.rs b/lib/rdf-query/src/traits/queryable.rs new file mode 100644 index 0000000..03eb159 --- /dev/null +++ b/lib/rdf-query/src/traits/queryable.rs @@ -0,0 +1,33 @@ +// This is free and unencumbered software released into the public domain. +extern crate alloc; + +use alloc::vec; +use rdf_model::Enumerable; + +use crate::{graph_pattern::GraphPattern, pattern::Pattern, query::Query, solutions::Solutions}; + +pub trait Queryable: Enumerable { + fn query(&self, pattern: impl Into) -> Solutions + where + Self: Sized, + { + match pattern.into() { + GraphPattern::BasicGraphPattern(query) => self.query_execute(query), + GraphPattern::TriplePattern(pattern) => self.query_execute(Query::new(vec![pattern])), + } + } + + fn query_execute(&self, query: Query) -> Solutions + where + Self: Sized, + { + query.execute(self) + } + + fn query_pattern(&self, pattern: &Pattern) -> Self + where + Self: Sized, + { + self.grep(pattern) + } +} diff --git a/lib/rdf-query/src/variable.rs b/lib/rdf-query/src/variable.rs new file mode 100644 index 0000000..f2a8a78 --- /dev/null +++ b/lib/rdf-query/src/variable.rs @@ -0,0 +1,64 @@ +extern crate alloc; + +use alloc::{boxed::Box, string::String}; +use rdf_model::Term; + +pub struct Variable { + name: String, + value: Option>, +} + +impl Variable { + pub fn bound(name: impl Into, value: impl Term + 'static) -> Self { + Self { + name: name.into(), + value: Some(Box::new(value)), + } + } + + pub fn unbound(name: impl Into) -> Self { + Self { + name: name.into(), + value: None, + } + } + + pub fn name(&self) -> &str { + &self.name + } +} + +impl PartialEq for Variable { + fn eq(&self, other: &Self) -> bool { + self.name == other.name + } +} + +impl Eq for Variable {} + +impl PartialOrd for Variable { + fn partial_cmp(&self, other: &Self) -> Option { + self.name.partial_cmp(&other.name) + } +} + +impl Ord for Variable { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + self.name.cmp(&other.name) + } +} + +impl From<&str> for Variable { + fn from(name: &str) -> Self { + Variable::unbound(name) + } +} + +impl core::fmt::Debug for Variable { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + f.debug_struct("Variable") + .field("name", &self.name) + .field("value", &self.value) + .finish() + } +} diff --git a/lib/rdf-reader/src/providers/oxrdf.rs b/lib/rdf-reader/src/providers/oxrdf.rs index 91f3bf6..4bcb20c 100644 --- a/lib/rdf-reader/src/providers/oxrdf.rs +++ b/lib/rdf-reader/src/providers/oxrdf.rs @@ -45,7 +45,14 @@ impl Reader for OxrdfReader { impl Source for OxrdfReader {} -impl Enumerable for OxrdfReader {} +impl Enumerable for OxrdfReader { + fn grep(&self, pattern: &impl PartialEq>) -> Self + where + Self: Sized, + { + todo!() + } +} impl Countable for OxrdfReader { fn count(&self) -> usize {