Skip to content
Open
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions lib/rdf-model/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,6 @@ mod traits {

mod maybe_mutable;
pub use maybe_mutable::*;

mod queryable;
pub use queryable::*;
}
pub use traits::*;

Expand Down
3 changes: 3 additions & 0 deletions lib/rdf-model/src/traits/enumerable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,7 @@ use core::error::Error;
pub trait Enumerable:
Countable + Iterator<Item = Result<Box<dyn Statement>, Box<dyn Error>>>
{
fn grep(&self, pattern: &impl PartialEq<Box<dyn Statement>>) -> Self
Copy link
Copy Markdown

@imunproductive imunproductive Feb 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

grep is not implemented for OxrdfReader and BorshReader

where
Self: Sized;
}
5 changes: 0 additions & 5 deletions lib/rdf-model/src/traits/queryable.rs

This file was deleted.

18 changes: 18 additions & 0 deletions lib/rdf-query/src/graph_pattern.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
use crate::{pattern::Pattern, query::Query};

pub enum GraphPattern {
BasicGraphPattern(Query),
TriplePattern(Pattern),
}

impl From<Query> for GraphPattern {
fn from(query: Query) -> Self {
Self::BasicGraphPattern(query)
}
}

impl From<Pattern> for GraphPattern {
fn from(pattern: Pattern) -> Self {
Self::TriplePattern(pattern)
}
}
12 changes: 12 additions & 0 deletions lib/rdf-query/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
56 changes: 56 additions & 0 deletions lib/rdf-query/src/matcher.rs
Original file line number Diff line number Diff line change
@@ -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<dyn Term>),
}

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<Variable> for Matcher {
fn from(var: Variable) -> Self {
Self::Variable(var)
}
}

impl From<&str> for Matcher {
fn from(name: &str) -> Self {
name.into()
}
}
Comment on lines +37 to +41
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This implementation is self-recursing.

warning: function cannot return without recursing
  --> lib/rdf-query/src/matcher.rs:38:5
   |
38 | /     fn from(name: &str) -> Self {
39 | |         name.into()
40 | |     }
   | |_____^
   |
note: recursive call site
  --> lib/rdf-query/src/matcher.rs:39:9
   |
39 |         name.into()
   |         ^^^^^^^^^^^
   = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#unconditional_recursion
   = note: `#[warn(clippy::unconditional_recursion)]` on by default
Suggested change
impl From<&str> for Matcher {
fn from(name: &str) -> Self {
name.into()
}
}
impl From<&str> for Matcher {
fn from(name: &str) -> Self {
Variable::from(name).into()
}
}


impl From<Box<dyn Term>> for Matcher {
fn from(term: Box<dyn Term>) -> 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()),
}
}
}
83 changes: 83 additions & 0 deletions lib/rdf-query/src/pattern.rs
Original file line number Diff line number Diff line change
@@ -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<Box<dyn Term>>,
}

impl Pattern {
pub fn new(
subject: impl Into<Matcher>,
predicate: impl Into<Matcher>,
object: impl Into<Matcher>,
graph_name: Option<Box<dyn Term>>,
) -> Self {
Self {
subject: subject.into(),
predicate: predicate.into(),
object: object.into(),
graph_name,
}
}

pub(crate) fn execute<Q: Queryable>(&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<dyn Statement>) -> 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<Box<dyn Statement>> for Pattern {
fn eq(&self, statement: &Box<dyn Statement>) -> 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()
}
}
41 changes: 41 additions & 0 deletions lib/rdf-query/src/query.rs
Original file line number Diff line number Diff line change
@@ -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<Pattern>,
}

impl Query {
pub fn new(patterns: Vec<Pattern>) -> Self {
Self { patterns }
}

/// Executes the query on the given graph.
///
/// If the query nas no patterns, it returns a single empty solution as per
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// If the query nas no patterns, it returns a single empty solution as per
/// If the query has no patterns, it returns a single empty solution as per

Copy link

Copilot AI May 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo in the documentation comment: 'nas' should be 'has'.

Suggested change
/// If the query nas no patterns, it returns a single empty solution as per
/// If the query has no patterns, it returns a single empty solution as per

Copilot uses AI. Check for mistakes.
/// SPARQL 1.1 Empty Group Pattern.
pub fn execute<Q: Queryable>(&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 {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional: I think this would be good to rename to match the idiomatic format rust uses. Also you're using empty elsewhere with different semantics (Solutions::empty() -> Solutions).

Suggested change
pub fn empty(&self) -> bool {
pub fn is_empty(&self) -> bool {

self.patterns.is_empty()
}
}
43 changes: 43 additions & 0 deletions lib/rdf-query/src/solution.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
extern crate alloc;

use alloc::collections::BTreeMap;
use rdf_model::HeapTerm;

use crate::variable::Variable;

pub struct Solution {
bindings: BTreeMap<Variable, HeapTerm>,
}

impl Solution {
pub fn new(bindings: BTreeMap<Variable, impl Into<HeapTerm>>) -> 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<Item = (&Variable, &HeapTerm)> {
self.bindings.iter()
}

pub fn each_name(&self) -> impl Iterator<Item = &Variable> {
self.bindings.keys()
}

pub fn each_value(&self) -> impl Iterator<Item = &HeapTerm> {
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()
}
}
37 changes: 37 additions & 0 deletions lib/rdf-query/src/solutions.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
extern crate alloc;

use alloc::boxed::Box;

use crate::solution::Solution;

pub struct Solutions {
iter: Box<dyn Iterator<Item = Solution>>,
}

impl Solutions {
pub fn new(iter: impl Iterator<Item = Solution> + '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::Item> {
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()
}
}
33 changes: 33 additions & 0 deletions lib/rdf-query/src/traits/queryable.rs
Original file line number Diff line number Diff line change
@@ -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<GraphPattern>) -> 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)
}
}
Loading