parent
8f94baedf6
commit
4f3f99b382
@ -1,4 +1,8 @@ |
||||
pub use crate::optimizer::Optimizer; |
||||
#[cfg(feature = "rules")] |
||||
pub use crate::reasoning::QueryRewriter; |
||||
|
||||
pub mod algebra; |
||||
mod optimizer; |
||||
#[cfg(feature = "rules")] |
||||
mod reasoning; |
||||
|
@ -0,0 +1,446 @@ |
||||
//! Reasoning via query rewriting
|
||||
|
||||
use crate::algebra::*; |
||||
use rand::random; |
||||
use spargebra::term::GroundTriplePattern; |
||||
use spargebra::RuleSet; |
||||
use std::collections::hash_map::Entry; |
||||
use std::collections::HashMap; |
||||
|
||||
pub struct QueryRewriter { |
||||
rules: Vec<(Vec<GroundTriplePattern>, GraphPattern)>, |
||||
} |
||||
|
||||
impl QueryRewriter { |
||||
pub fn new(rule_set: RuleSet) -> Self { |
||||
Self { |
||||
rules: rule_set |
||||
.rules |
||||
.into_iter() |
||||
.map(|rule| (rule.head, (&rule.body).into())) |
||||
.collect(), |
||||
} |
||||
} |
||||
|
||||
pub fn rewrite_graph_pattern(&self, pattern: &GraphPattern) -> GraphPattern { |
||||
//TODO: rewrite EXISTS
|
||||
match pattern { |
||||
GraphPattern::QuadPattern { |
||||
subject, |
||||
predicate, |
||||
object, |
||||
graph_name, |
||||
} => self.rewrite_quad_pattern(subject, predicate, object, graph_name.as_ref()), |
||||
GraphPattern::Path { .. } => todo!(), |
||||
GraphPattern::Join { left, right } => GraphPattern::join( |
||||
self.rewrite_graph_pattern(left), |
||||
self.rewrite_graph_pattern(right), |
||||
), |
||||
GraphPattern::LeftJoin { |
||||
left, |
||||
right, |
||||
expression, |
||||
} => GraphPattern::left_join( |
||||
self.rewrite_graph_pattern(left), |
||||
self.rewrite_graph_pattern(right), |
||||
expression.clone(), |
||||
), |
||||
#[cfg(feature = "sep-0006")] |
||||
GraphPattern::Lateral { left, right } => GraphPattern::lateral( |
||||
self.rewrite_graph_pattern(left), |
||||
self.rewrite_graph_pattern(right), |
||||
), |
||||
GraphPattern::Filter { inner, expression } => { |
||||
GraphPattern::filter(self.rewrite_graph_pattern(inner), expression.clone()) |
||||
} |
||||
GraphPattern::Union { inner } => inner |
||||
.iter() |
||||
.map(|p| self.rewrite_graph_pattern(p)) |
||||
.reduce(GraphPattern::union) |
||||
.unwrap_or_else(GraphPattern::empty), |
||||
GraphPattern::Extend { |
||||
inner, |
||||
variable, |
||||
expression, |
||||
} => GraphPattern::extend( |
||||
self.rewrite_graph_pattern(inner), |
||||
variable.clone(), |
||||
expression.clone(), |
||||
), |
||||
GraphPattern::Minus { left, right } => GraphPattern::minus( |
||||
self.rewrite_graph_pattern(left), |
||||
self.rewrite_graph_pattern(right), |
||||
), |
||||
GraphPattern::Values { |
||||
variables, |
||||
bindings, |
||||
} => GraphPattern::values(variables.clone(), bindings.clone()), |
||||
GraphPattern::OrderBy { inner, expression } => { |
||||
GraphPattern::order_by(self.rewrite_graph_pattern(inner), expression.clone()) |
||||
} |
||||
GraphPattern::Project { inner, variables } => { |
||||
GraphPattern::project(self.rewrite_graph_pattern(inner), variables.clone()) |
||||
} |
||||
GraphPattern::Distinct { inner } => { |
||||
GraphPattern::distinct(self.rewrite_graph_pattern(inner)) |
||||
} |
||||
GraphPattern::Reduced { inner } => { |
||||
GraphPattern::reduced(self.rewrite_graph_pattern(inner)) |
||||
} |
||||
GraphPattern::Slice { |
||||
inner, |
||||
start, |
||||
length, |
||||
} => GraphPattern::slice(self.rewrite_graph_pattern(inner), *start, *length), |
||||
GraphPattern::Group { |
||||
inner, |
||||
variables, |
||||
aggregates, |
||||
} => GraphPattern::group( |
||||
self.rewrite_graph_pattern(inner), |
||||
variables.clone(), |
||||
aggregates.clone(), |
||||
), |
||||
GraphPattern::Service { |
||||
inner, |
||||
silent, |
||||
name, |
||||
} => GraphPattern::service(self.rewrite_graph_pattern(inner), name.clone(), *silent), |
||||
} |
||||
} |
||||
|
||||
fn rewrite_quad_pattern( |
||||
&self, |
||||
subject: &GroundTermPattern, |
||||
predicate: &NamedNodePattern, |
||||
object: &GroundTermPattern, |
||||
graph_name: Option<&NamedNodePattern>, |
||||
) -> GraphPattern { |
||||
// We rewrite based on rules
|
||||
let mut graph_pattern = GraphPattern::QuadPattern { |
||||
subject: subject.clone(), |
||||
predicate: predicate.clone(), |
||||
object: object.clone(), |
||||
graph_name: graph_name.cloned(), |
||||
}; |
||||
for (rule_head, rule_body) in &self.rules { |
||||
for head_pattern in rule_head { |
||||
if let Some(nested) = self.apply_rule_for_quad_pattern( |
||||
subject, |
||||
predicate, |
||||
object, |
||||
graph_name, |
||||
head_pattern, |
||||
rule_body, |
||||
) { |
||||
graph_pattern = GraphPattern::union(graph_pattern, nested); |
||||
} |
||||
} |
||||
} |
||||
graph_pattern |
||||
} |
||||
|
||||
/// Attempts to use a given rule to get new facts for a triple pattern
|
||||
fn apply_rule_for_quad_pattern( |
||||
&self, |
||||
subject: &GroundTermPattern, |
||||
predicate: &NamedNodePattern, |
||||
object: &GroundTermPattern, |
||||
graph_name: Option<&NamedNodePattern>, |
||||
head: &GroundTriplePattern, |
||||
body: &GraphPattern, |
||||
) -> Option<GraphPattern> { |
||||
let head_unification = Self::unify_triple_pattern( |
||||
subject.clone(), |
||||
predicate.clone(), |
||||
object.clone(), |
||||
head.clone(), |
||||
)?; |
||||
// We build a nested query
|
||||
// from is the parent query and to is the nested one
|
||||
let mut replacements_in_rule = HashMap::new(); |
||||
let mut final_binds = Vec::new(); |
||||
for replacement in head_unification { |
||||
match replacement { |
||||
Replacement::ConstToVar { from, to } => match replacements_in_rule.entry(to) { |
||||
Entry::Vacant(e) => { |
||||
e.insert(TermOrVariable::Term(from)); |
||||
} |
||||
Entry::Occupied(mut e) => match e.get() { |
||||
TermOrVariable::Term(c) => { |
||||
if from != *c { |
||||
return None; //Conflict
|
||||
} |
||||
} |
||||
TermOrVariable::Variable(v) => { |
||||
final_binds.push((v.clone(), TermOrVariable::Term(from.clone()))); |
||||
e.insert(TermOrVariable::Term(from)); |
||||
} |
||||
}, |
||||
}, |
||||
Replacement::VarToConst { from, to } => { |
||||
final_binds.push((from, TermOrVariable::Term(to))); |
||||
} |
||||
Replacement::VarToVar { from, to } => match replacements_in_rule.entry(to) { |
||||
Entry::Vacant(e) => { |
||||
e.insert(TermOrVariable::Variable(from)); |
||||
} |
||||
Entry::Occupied(e) => final_binds.push((from, e.get().clone())), |
||||
}, |
||||
} |
||||
} |
||||
let mut plan = self.rewrite_rule_body(body, graph_name, &mut replacements_in_rule)?; |
||||
for (variable, value) in final_binds { |
||||
plan = GraphPattern::extend( |
||||
plan, |
||||
variable, |
||||
match value { |
||||
TermOrVariable::Term(v) => v.into(), |
||||
TermOrVariable::Variable(v) => v.into(), |
||||
}, |
||||
); |
||||
} |
||||
Some(plan) |
||||
} |
||||
|
||||
fn rewrite_rule_body<'a>( |
||||
&self, |
||||
pattern: &'a GraphPattern, |
||||
parent_graph_name: Option<&'a NamedNodePattern>, |
||||
replacements_in_rule: &mut HashMap<Variable, TermOrVariable>, |
||||
) -> Option<GraphPattern> { |
||||
Some(match pattern { |
||||
GraphPattern::QuadPattern { |
||||
subject, |
||||
predicate, |
||||
object, |
||||
graph_name, |
||||
} => self.rewrite_quad_pattern( |
||||
&Self::apply_replacement_on_term_pattern(subject, replacements_in_rule)?, |
||||
&Self::apply_replacement_on_named_node_pattern(predicate, replacements_in_rule)?, |
||||
&Self::apply_replacement_on_term_pattern(object, replacements_in_rule)?, |
||||
if let Some(graph_name) = graph_name { |
||||
Some(Self::apply_replacement_on_named_node_pattern( |
||||
graph_name, |
||||
replacements_in_rule, |
||||
)?) |
||||
} else { |
||||
parent_graph_name.cloned() |
||||
} |
||||
.as_ref(), |
||||
), |
||||
GraphPattern::Join { left, right } => GraphPattern::join( |
||||
self.rewrite_rule_body(left, parent_graph_name, replacements_in_rule)?, |
||||
self.rewrite_rule_body(right, parent_graph_name, replacements_in_rule)?, |
||||
), |
||||
GraphPattern::Values { |
||||
variables, |
||||
bindings, |
||||
} => { |
||||
let variable_mapping = variables |
||||
.iter() |
||||
.map(|v| { |
||||
replacements_in_rule |
||||
.entry(v.clone()) |
||||
.or_insert_with(|| TermOrVariable::Variable(new_var())) |
||||
.clone() |
||||
}) |
||||
.collect::<Vec<_>>(); |
||||
GraphPattern::Values { |
||||
variables: variable_mapping |
||||
.iter() |
||||
.filter_map(|v| match v { |
||||
TermOrVariable::Term(_) => None, |
||||
TermOrVariable::Variable(v) => Some(v.clone()), |
||||
}) |
||||
.collect(), |
||||
bindings: bindings |
||||
.iter() |
||||
.filter_map(|binding| { |
||||
let mut new_binding = Vec::with_capacity(binding.len()); |
||||
for (variable, value) in variable_mapping.iter().zip(binding) { |
||||
match variable { |
||||
TermOrVariable::Variable(_) => new_binding.push(value.clone()), |
||||
TermOrVariable::Term(cst) => { |
||||
let compatible = if let Some(value) = value { |
||||
cst == value |
||||
} else { |
||||
true |
||||
}; |
||||
if !compatible { |
||||
return None; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
Some(new_binding) |
||||
}) |
||||
.collect(), |
||||
} |
||||
} |
||||
_ => unreachable!("Not allowed by the parser yet: {pattern:?}"), |
||||
}) |
||||
} |
||||
|
||||
fn apply_replacement_on_named_node_pattern( |
||||
pattern: &NamedNodePattern, |
||||
replacements: &mut HashMap<Variable, TermOrVariable>, |
||||
) -> Option<NamedNodePattern> { |
||||
Some(match pattern { |
||||
NamedNodePattern::NamedNode(node) => NamedNodePattern::NamedNode(node.clone()), |
||||
NamedNodePattern::Variable(variable) => { |
||||
match replacements |
||||
.entry(variable.clone()) |
||||
.or_insert_with(|| TermOrVariable::Variable(new_var())) |
||||
{ |
||||
TermOrVariable::Term(c) => { |
||||
if let GroundTerm::NamedNode(node) = c { |
||||
NamedNodePattern::NamedNode(node.clone()) |
||||
} else { |
||||
return None; |
||||
} |
||||
} |
||||
TermOrVariable::Variable(v) => NamedNodePattern::Variable(v.clone()), |
||||
} |
||||
} |
||||
}) |
||||
} |
||||
|
||||
fn apply_replacement_on_term_pattern( |
||||
pattern: &GroundTermPattern, |
||||
replacements: &mut HashMap<Variable, TermOrVariable>, |
||||
) -> Option<GroundTermPattern> { |
||||
Some(match pattern { |
||||
GroundTermPattern::NamedNode(node) => node.clone().into(), |
||||
GroundTermPattern::Literal(literal) => literal.clone().into(), |
||||
GroundTermPattern::Triple(triple) => GroundTriplePattern { |
||||
subject: Self::apply_replacement_on_term_pattern(&triple.subject, replacements)?, |
||||
predicate: Self::apply_replacement_on_named_node_pattern( |
||||
&triple.predicate, |
||||
replacements, |
||||
)?, |
||||
object: Self::apply_replacement_on_term_pattern(&triple.subject, replacements)?, |
||||
} |
||||
.into(), |
||||
GroundTermPattern::Variable(variable) => { |
||||
match replacements |
||||
.entry(variable.clone()) |
||||
.or_insert_with(|| TermOrVariable::Variable(new_var())) |
||||
{ |
||||
TermOrVariable::Term(c) => c.clone().into(), |
||||
TermOrVariable::Variable(v) => v.clone().into(), |
||||
} |
||||
} |
||||
}) |
||||
} |
||||
|
||||
fn unify_triple_pattern( |
||||
from_subject: GroundTermPattern, |
||||
from_predicate: NamedNodePattern, |
||||
from_object: GroundTermPattern, |
||||
to: GroundTriplePattern, |
||||
) -> Option<Vec<Replacement>> { |
||||
let mut mapping = Self::unify_ground_term_pattern(from_subject, to.subject)?; |
||||
mapping.extend(Self::unify_named_node_pattern( |
||||
from_predicate, |
||||
to.predicate, |
||||
)?); |
||||
mapping.extend(Self::unify_ground_term_pattern(from_object, to.object)?); |
||||
Some(mapping) |
||||
} |
||||
|
||||
fn unify_named_node_pattern( |
||||
from: NamedNodePattern, |
||||
to: NamedNodePattern, |
||||
) -> Option<Vec<Replacement>> { |
||||
match from { |
||||
NamedNodePattern::NamedNode(from) => match to { |
||||
NamedNodePattern::NamedNode(to) => { |
||||
if from == to { |
||||
Some(Vec::new()) |
||||
} else { |
||||
None |
||||
} |
||||
} |
||||
NamedNodePattern::Variable(to) => Some(vec![Replacement::ConstToVar { |
||||
from: from.into(), |
||||
to, |
||||
}]), |
||||
}, |
||||
NamedNodePattern::Variable(from) => match to { |
||||
NamedNodePattern::NamedNode(to) => Some(vec![Replacement::VarToConst { |
||||
from, |
||||
to: to.into(), |
||||
}]), |
||||
NamedNodePattern::Variable(to) => Some(vec![Replacement::VarToVar { from, to }]), |
||||
}, |
||||
} |
||||
} |
||||
|
||||
fn unify_ground_term_pattern( |
||||
from: GroundTermPattern, |
||||
to: GroundTermPattern, |
||||
) -> Option<Vec<Replacement>> { |
||||
match from { |
||||
GroundTermPattern::NamedNode(from) => match to { |
||||
GroundTermPattern::NamedNode(to) => { |
||||
if from == to { |
||||
Some(Vec::new()) |
||||
} else { |
||||
None |
||||
} |
||||
} |
||||
GroundTermPattern::Literal(_) | GroundTermPattern::Triple(_) => None, |
||||
GroundTermPattern::Variable(to) => Some(vec![Replacement::ConstToVar { |
||||
from: from.into(), |
||||
to, |
||||
}]), |
||||
}, |
||||
GroundTermPattern::Literal(from) => match to { |
||||
GroundTermPattern::NamedNode(_) => None, |
||||
GroundTermPattern::Literal(to) => { |
||||
if from == to { |
||||
Some(Vec::new()) |
||||
} else { |
||||
None |
||||
} |
||||
} |
||||
GroundTermPattern::Triple(_) => None, |
||||
GroundTermPattern::Variable(to) => Some(vec![Replacement::ConstToVar { |
||||
from: from.into(), |
||||
to, |
||||
}]), |
||||
}, |
||||
GroundTermPattern::Triple(_) => unimplemented!(), |
||||
GroundTermPattern::Variable(from) => match to { |
||||
GroundTermPattern::NamedNode(to) => Some(vec![Replacement::VarToConst { |
||||
from, |
||||
to: to.into(), |
||||
}]), |
||||
GroundTermPattern::Literal(to) => Some(vec![Replacement::VarToConst { |
||||
from, |
||||
to: to.into(), |
||||
}]), |
||||
GroundTermPattern::Triple(_) => unimplemented!(), |
||||
GroundTermPattern::Variable(to) => Some(vec![Replacement::VarToVar { from, to }]), |
||||
}, |
||||
} |
||||
} |
||||
} |
||||
|
||||
#[derive(Clone)] |
||||
enum Replacement { |
||||
VarToConst { from: Variable, to: GroundTerm }, |
||||
ConstToVar { from: GroundTerm, to: Variable }, |
||||
VarToVar { from: Variable, to: Variable }, |
||||
} |
||||
|
||||
#[derive(Clone)] |
||||
enum TermOrVariable { |
||||
Term(GroundTerm), |
||||
Variable(Variable), |
||||
} |
||||
|
||||
fn new_var() -> Variable { |
||||
Variable::new_unchecked(format!("{:x}", random::<u128>())) |
||||
} |
@ -0,0 +1,39 @@ |
||||
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> . |
||||
@prefix : <https://github.com/oxigraph/oxigraph/tests/sparql/manifest#> . |
||||
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> . |
||||
@prefix mf: <http://www.w3.org/2001/sw/DataAccess/tests/test-manifest#> . |
||||
@prefix qt: <http://www.w3.org/2001/sw/DataAccess/tests/test-query#> . |
||||
@prefix ox: <https://github.com/oxigraph/oxigraph/tests#> . |
||||
|
||||
<> rdf:type mf:Manifest ; |
||||
rdfs:label "Oxigraph SPARQL reasoning tests" ; |
||||
mf:entries |
||||
( |
||||
:simple_type_inheritance |
||||
:simple_fact |
||||
:with_graph_name |
||||
) . |
||||
|
||||
:simple_type_inheritance rdf:type ox:SparqlRuleEvaluationTest ; |
||||
mf:name "Simple type inheritance" ; |
||||
mf:action |
||||
[ qt:query <simple_type_inheritance.rq> ; |
||||
qt:data <simple_type_inheritance.ttl> ] ; |
||||
ox:rulesData <simple_type_inheritance.rr> ; |
||||
mf:result <simple_type_inheritance.srj> . |
||||
|
||||
:simple_fact rdf:type ox:SparqlRuleEvaluationTest ; |
||||
mf:name "Simple fact" ; |
||||
mf:action |
||||
[ qt:query <simple_fact.rq> ; |
||||
qt:data <simple_fact.ttl> ] ; |
||||
ox:rulesData <simple_fact.rr> ; |
||||
mf:result <simple_fact.srj> . |
||||
|
||||
:with_graph_name rdf:type ox:SparqlRuleEvaluationTest ; |
||||
mf:name "Simple type inheritance" ; |
||||
mf:action |
||||
[ qt:query <with_graph_name.rq> ; |
||||
qt:graphData <with_graph_name.ttl> ] ; |
||||
ox:rulesData <with_graph_name.rr> ; |
||||
mf:result <with_graph_name.srj> . |
@ -0,0 +1,3 @@ |
||||
SELECT * WHERE { |
||||
?s ?p ?o |
||||
} |
@ -0,0 +1,3 @@ |
||||
PREFIX ex: <http://example.org/> |
||||
|
||||
IF {} THEN { ex:s ex:p ex:o } |
@ -0,0 +1,23 @@ |
||||
{ |
||||
"head": { |
||||
"vars": ["s", "p", "o"] |
||||
}, |
||||
"results": { |
||||
"bindings": [ |
||||
{ |
||||
"s": { |
||||
"type": "uri", |
||||
"value": "http://example.org/s" |
||||
}, |
||||
"p": { |
||||
"type": "uri", |
||||
"value": "http://example.org/p" |
||||
}, |
||||
"o": { |
||||
"type": "uri", |
||||
"value": "http://example.org/o" |
||||
} |
||||
} |
||||
] |
||||
} |
||||
} |
@ -0,0 +1,5 @@ |
||||
PREFIX ex: <http://example.org/> |
||||
|
||||
SELECT * WHERE { |
||||
?s a ex:Bar |
||||
} |
@ -0,0 +1,3 @@ |
||||
PREFIX ex: <http://example.org/> |
||||
|
||||
IF { ?s a ex:Foo } THEN { ?s a ex:Bar } |
@ -0,0 +1,15 @@ |
||||
{ |
||||
"head": { |
||||
"vars": ["s"] |
||||
}, |
||||
"results": { |
||||
"bindings": [ |
||||
{ |
||||
"s": { |
||||
"type": "uri", |
||||
"value": "http://example.org/s1" |
||||
} |
||||
} |
||||
] |
||||
} |
||||
} |
@ -0,0 +1,2 @@ |
||||
PREFIX ex: <http://example.org/> |
||||
ex:s1 a ex:Foo . |
@ -0,0 +1,5 @@ |
||||
PREFIX ex: <http://example.org/> |
||||
|
||||
SELECT * WHERE { |
||||
GRAPH ?g { ?s a ex:Bar } |
||||
} |
@ -0,0 +1,3 @@ |
||||
PREFIX ex: <http://example.org/> |
||||
|
||||
IF { ?g a ex:Foo } THEN { ?g a ex:Bar } |
@ -0,0 +1,19 @@ |
||||
{ |
||||
"head": { |
||||
"vars": ["s", "g"] |
||||
}, |
||||
"results": { |
||||
"bindings": [ |
||||
{ |
||||
"s": { |
||||
"type": "uri", |
||||
"value": "http://example.org/s1" |
||||
}, |
||||
"g": { |
||||
"type": "uri", |
||||
"value": "https://github.com/oxigraph/oxigraph/tests/sparql-reasoning/with_graph_name.ttl" |
||||
} |
||||
} |
||||
] |
||||
} |
||||
} |
@ -0,0 +1,2 @@ |
||||
PREFIX ex: <http://example.org/> |
||||
ex:s1 a ex:Foo . |
Loading…
Reference in new issue