diff --git a/lib/src/sparql/eval.rs b/lib/src/sparql/eval.rs index 72c730c5..2d299719 100644 --- a/lib/src/sparql/eval.rs +++ b/lib/src/sparql/eval.rs @@ -20,6 +20,7 @@ use rio_api::model as rio; use rust_decimal::{Decimal, RoundingStrategy}; use sha1::Sha1; use sha2::{Sha256, Sha384, Sha512}; +use std::cmp::min; use std::cmp::Ordering; use std::collections::BTreeMap; use std::collections::HashSet; @@ -207,6 +208,17 @@ impl<'a, S: StoreConnection + 'a> SimpleEvaluator { buffered_results: errors, }) } + PlanNode::AntiJoin { left, right } => { + //TODO: dumb implementation + let right: Vec<_> = self + .eval_plan(&*right, from.clone()) + .filter_map(|result| result.ok()) + .collect(); + Box::new(AntiJoinIterator { + left_iter: self.eval_plan(&*left, from), + right, + }) + } PlanNode::LeftJoin { left, right, @@ -1713,6 +1725,22 @@ fn combine_tuples(a: &[Option], b: &[Option]) -> Optio } } +fn are_tuples_compatible_and_not_disjointed( + a: &[Option], + b: &[Option], +) -> bool { + let mut found_intersection = false; + for i in 0..min(a.len(), b.len()) { + if let (Some(a_value), Some(b_value)) = (a[i], b[i]) { + if a_value != b_value { + return false; + } + found_intersection = true; + } + } + found_intersection +} + struct JoinIterator<'a> { left: Vec, right_iter: EncodedTuplesIterator<'a>, @@ -1739,6 +1767,31 @@ impl<'a> Iterator for JoinIterator<'a> { } } +struct AntiJoinIterator<'a> { + left_iter: EncodedTuplesIterator<'a>, + right: Vec, +} + +impl<'a> Iterator for AntiJoinIterator<'a> { + type Item = Result; + + fn next(&mut self) -> Option> { + loop { + match self.left_iter.next()? { + Ok(left_tuple) => { + let exists_compatible_right = self.right.iter().any(|right_tuple| { + are_tuples_compatible_and_not_disjointed(&left_tuple, right_tuple) + }); + if !exists_compatible_right { + return Some(Ok(left_tuple)); + } + } + Err(error) => return Some(Err(error)), + } + } + } +} + struct LeftJoinIterator<'a, S: StoreConnection + 'a> { eval: SimpleEvaluator, right_plan: &'a PlanNode, diff --git a/lib/src/sparql/plan.rs b/lib/src/sparql/plan.rs index 91ff9ea9..663a8faf 100644 --- a/lib/src/sparql/plan.rs +++ b/lib/src/sparql/plan.rs @@ -20,6 +20,10 @@ pub enum PlanNode { left: Box, right: Box, }, + AntiJoin { + left: Box, + right: Box, + }, Filter { child: Box, expression: PlanExpression, @@ -109,11 +113,9 @@ impl PlanNode { child.add_variables(set); } } - PlanNode::Join { left, right } => { - left.add_variables(set); - right.add_variables(set); - } - PlanNode::LeftJoin { left, right, .. } => { + PlanNode::Join { left, right } + | PlanNode::AntiJoin { left, right } + | PlanNode::LeftJoin { left, right, .. } => { left.add_variables(set); right.add_variables(set); } diff --git a/lib/src/sparql/plan_builder.rs b/lib/src/sparql/plan_builder.rs index 64841fd6..8f43c5fb 100644 --- a/lib/src/sparql/plan_builder.rs +++ b/lib/src/sparql/plan_builder.rs @@ -113,7 +113,15 @@ impl<'a, S: StoreConnection> PlanBuilder<'a, S> { position: variable_key(variables, &v), expression: self.build_for_expression(e, variables, graph_name)?, }, - GraphPattern::Minus(_a, _b) => unimplemented!(), + GraphPattern::Minus(a, b) => PlanNode::AntiJoin { + left: Box::new(self.build_for_graph_pattern( + a, + input.clone(), + variables, + graph_name, + )?), + right: Box::new(self.build_for_graph_pattern(b, input, variables, graph_name)?), + }, GraphPattern::Service(_n, _p, _s) => unimplemented!(), GraphPattern::AggregateJoin(_g, _a) => unimplemented!(), GraphPattern::Data(bs) => PlanNode::StaticBindings { diff --git a/lib/tests/sparql_test_cases.rs b/lib/tests/sparql_test_cases.rs index d8ece42b..dd2b2662 100644 --- a/lib/tests/sparql_test_cases.rs +++ b/lib/tests/sparql_test_cases.rs @@ -91,6 +91,7 @@ fn sparql_w3c_query_evaluation_testsuite() -> Result<()> { "http://www.w3.org/2009/sparql/docs/tests/data-sparql11/construct/manifest.ttl", "http://www.w3.org/2009/sparql/docs/tests/data-sparql11/exists/manifest.ttl", "http://www.w3.org/2009/sparql/docs/tests/data-sparql11/functions/manifest.ttl", + "http://www.w3.org/2009/sparql/docs/tests/data-sparql11/negation/manifest.ttl", ]; let test_blacklist = vec![