Fixes ZeroOrX path evaluation on terms that are not in the dataset but only in the query

pull/319/head
Tpt 2 years ago committed by Thomas Tanon
parent 07b5c32935
commit b6c9a5b429
  1. 90
      lib/src/sparql/eval.rs
  2. 7
      testsuite/oxigraph-tests/sparql/manifest.ttl
  3. 4
      testsuite/oxigraph-tests/sparql/values_property_path_all.rq
  4. 8
      testsuite/oxigraph-tests/sparql/values_property_path_all.srx

@ -769,12 +769,14 @@ impl SimpleEvaluator {
.chain(self.eval_path_from(b, start, graph_name)), .chain(self.eval_path_from(b, start, graph_name)),
), ),
PlanPropertyPath::ZeroOrMore(p) => { PlanPropertyPath::ZeroOrMore(p) => {
let eval = self.clone(); self.run_if_term_is_a_graph_node(start, graph_name, || {
let p = p.clone(); let eval = self.clone();
let graph_name2 = graph_name.clone(); let p = p.clone();
Box::new(transitive_closure(Some(Ok(start.clone())), move |e| { let graph_name2 = graph_name.clone();
eval.eval_path_from(&p, &e, &graph_name2) Box::new(transitive_closure(Some(Ok(start.clone())), move |e| {
})) eval.eval_path_from(&p, &e, &graph_name2)
}))
})
} }
PlanPropertyPath::OneOrMore(p) => { PlanPropertyPath::OneOrMore(p) => {
let eval = self.clone(); let eval = self.clone();
@ -785,9 +787,13 @@ impl SimpleEvaluator {
move |e| eval.eval_path_from(&p, &e, &graph_name2), move |e| eval.eval_path_from(&p, &e, &graph_name2),
)) ))
} }
PlanPropertyPath::ZeroOrOne(p) => Box::new(hash_deduplicate( PlanPropertyPath::ZeroOrOne(p) => {
once(Ok(start.clone())).chain(self.eval_path_from(p, start, graph_name)), self.run_if_term_is_a_graph_node(start, graph_name, || {
)), Box::new(hash_deduplicate(
once(Ok(start.clone())).chain(self.eval_path_from(p, start, graph_name)),
))
})
}
PlanPropertyPath::NegatedPropertySet(ps) => { PlanPropertyPath::NegatedPropertySet(ps) => {
let ps = ps.clone(); let ps = ps.clone();
Box::new( Box::new(
@ -835,12 +841,14 @@ impl SimpleEvaluator {
.chain(self.eval_path_to(b, end, graph_name)), .chain(self.eval_path_to(b, end, graph_name)),
), ),
PlanPropertyPath::ZeroOrMore(p) => { PlanPropertyPath::ZeroOrMore(p) => {
let eval = self.clone(); self.run_if_term_is_a_graph_node(end, graph_name, || {
let p = p.clone(); let eval = self.clone();
let graph_name2 = graph_name.clone(); let p = p.clone();
Box::new(transitive_closure(Some(Ok(end.clone())), move |e| { let graph_name2 = graph_name.clone();
eval.eval_path_to(&p, &e, &graph_name2) Box::new(transitive_closure(Some(Ok(end.clone())), move |e| {
})) eval.eval_path_to(&p, &e, &graph_name2)
}))
})
} }
PlanPropertyPath::OneOrMore(p) => { PlanPropertyPath::OneOrMore(p) => {
let eval = self.clone(); let eval = self.clone();
@ -851,9 +859,13 @@ impl SimpleEvaluator {
move |e| eval.eval_path_to(&p, &e, &graph_name2), move |e| eval.eval_path_to(&p, &e, &graph_name2),
)) ))
} }
PlanPropertyPath::ZeroOrOne(p) => Box::new(hash_deduplicate( PlanPropertyPath::ZeroOrOne(p) => {
once(Ok(end.clone())).chain(self.eval_path_to(p, end, graph_name)), self.run_if_term_is_a_graph_node(end, graph_name, || {
)), Box::new(hash_deduplicate(
once(Ok(end.clone())).chain(self.eval_path_to(p, end, graph_name)),
))
})
}
PlanPropertyPath::NegatedPropertySet(ps) => { PlanPropertyPath::NegatedPropertySet(ps) => {
let ps = ps.clone(); let ps = ps.clone();
Box::new( Box::new(
@ -959,8 +971,46 @@ impl SimpleEvaluator {
) -> impl Iterator<Item = Result<(EncodedTerm, EncodedTerm), EvaluationError>> { ) -> impl Iterator<Item = Result<(EncodedTerm, EncodedTerm), EvaluationError>> {
self.dataset self.dataset
.encoded_quads_for_pattern(None, None, None, Some(graph_name)) .encoded_quads_for_pattern(None, None, None, Some(graph_name))
.flat_map_ok(|t| once(Ok(t.subject)).chain(once(Ok(t.object)))) .flat_map_ok(|t| {
.map(|e| e.map(|e| (e.clone(), e))) [
Ok((t.subject.clone(), t.subject)),
Ok((t.object.clone(), t.object)),
]
})
}
fn run_if_term_is_a_graph_node<T: 'static>(
&self,
term: &EncodedTerm,
graph_name: &EncodedTerm,
f: impl FnOnce() -> Box<dyn Iterator<Item = Result<T, EvaluationError>>>,
) -> Box<dyn Iterator<Item = Result<T, EvaluationError>>> {
match self.is_subject_or_object_in_dataset(term, graph_name) {
Ok(true) => f(),
Ok(false) => {
Box::new(empty()) // Not in the database
}
Err(error) => Box::new(once(Err(error))),
}
}
fn is_subject_or_object_in_dataset(
&self,
term: &EncodedTerm,
graph_name: &EncodedTerm,
) -> Result<bool, EvaluationError> {
Ok(self
.dataset
.encoded_quads_for_pattern(Some(term), None, None, Some(graph_name))
.next()
.transpose()?
.is_some()
|| self
.dataset
.encoded_quads_for_pattern(None, None, Some(term), Some(graph_name))
.next()
.transpose()?
.is_some())
} }
#[allow(clippy::cast_possible_truncation, clippy::cast_precision_loss)] #[allow(clippy::cast_possible_truncation, clippy::cast_precision_loss)]

@ -30,6 +30,7 @@
:unbound_variable_in_subquery :unbound_variable_in_subquery
:values_too_many :values_too_many
:values_too_few :values_too_few
:values_property_path_all
) . ) .
:small_unicode_escape_with_multibytes_char rdf:type mf:NegativeSyntaxTest ; :small_unicode_escape_with_multibytes_char rdf:type mf:NegativeSyntaxTest ;
@ -139,3 +140,9 @@
:values_too_few rdf:type mf:NegativeSyntaxTest11 ; :values_too_few rdf:type mf:NegativeSyntaxTest11 ;
mf:name "Too few values in a VALUE clause compared to the number of variable" ; mf:name "Too few values in a VALUE clause compared to the number of variable" ;
mf:action <values_too_few.rq> . mf:action <values_too_few.rq> .
:values_property_path_all rdf:type mf:QueryEvaluationTest ;
mf:name "ZeroOrX property paths should only return terms in the graph and not also terms defined in the query" ;
mf:action
[ qt:query <values_property_path_all.rq> ] ;
mf:result <values_property_path_all.srx> .

@ -0,0 +1,4 @@
SELECT * WHERE {
VALUES ?v { 1 }
?v <http://example.com/p>? ?v
}

@ -0,0 +1,8 @@
<?xml version="1.0"?>
<sparql xmlns="http://www.w3.org/2005/sparql-results#">
<head>
<variable name="v"/>
</head>
<results>
</results>
</sparql>
Loading…
Cancel
Save