Implements LATERAL join

Follows SPARQL 1.2 SEP 0006. It is behind a sep-0006 feature in spargebra and sparql-smith and enabled by default in oxigraph.

Apache Jena tests are passing.

SEP 0006: https://github.com/w3c/sparql-12/blob/main/SEP/SEP-0006/sep-0006.md
pull/341/head
Tpt 2 years ago committed by Thomas Tanon
parent 3f3523963d
commit 76dd879ea6
  1. 4
      fuzz/Cargo.toml
  2. 2
      lib/Cargo.toml
  3. 1
      lib/spargebra/Cargo.toml
  4. 42
      lib/spargebra/src/algebra.rs
  5. 85
      lib/spargebra/src/parser.rs
  6. 4
      lib/sparql-smith/Cargo.toml
  7. 16
      lib/sparql-smith/src/lib.rs
  8. 98
      lib/src/sparql/eval.rs
  9. 6
      lib/src/sparql/plan.rs
  10. 21
      lib/src/sparql/plan_builder.rs
  11. 5
      testsuite/oxigraph-tests/sparql/lateral/basic_input.ttl
  12. 6
      testsuite/oxigraph-tests/sparql/lateral/filter.rq
  13. 6
      testsuite/oxigraph-tests/sparql/lateral/graph.rq
  14. 9
      testsuite/oxigraph-tests/sparql/lateral/join.rq
  15. 68
      testsuite/oxigraph-tests/sparql/lateral/manifest.ttl
  16. 6
      testsuite/oxigraph-tests/sparql/lateral/optional.rq
  17. 17
      testsuite/oxigraph-tests/sparql/lateral/simple.srx
  18. 6
      testsuite/oxigraph-tests/sparql/lateral/subselect.rq
  19. 41
      testsuite/oxigraph-tests/sparql/lateral/subselect.srx
  20. 6
      testsuite/oxigraph-tests/sparql/lateral/subselect_aggregate.rq
  21. 25
      testsuite/oxigraph-tests/sparql/lateral/subselect_aggregate.srx
  22. 6
      testsuite/oxigraph-tests/sparql/lateral/subselect_inside_optional.rq
  23. 35
      testsuite/oxigraph-tests/sparql/lateral/subselect_inside_optional.srx
  24. 6
      testsuite/oxigraph-tests/sparql/lateral/subselect_outside_optional.rq
  25. 46
      testsuite/oxigraph-tests/sparql/lateral/subselect_outside_optional.srx
  26. 1
      testsuite/oxigraph-tests/sparql/manifest.ttl
  27. 7
      testsuite/tests/oxigraph.rs
  28. 7
      testsuite/tests/parser.rs
  29. 7
      testsuite/tests/sparql.rs

@ -11,9 +11,9 @@ cargo-fuzz = true
[dependencies] [dependencies]
lazy_static = "1" lazy_static = "1"
libfuzzer-sys = "0.4" libfuzzer-sys = "0.4"
spargebra = { path = "../lib/spargebra", features = ["rdf-star"] } spargebra = { path = "../lib/spargebra", features = ["rdf-star", "sep-0006"] }
sparesults = { path = "../lib/sparesults", features = ["rdf-star"] } sparesults = { path = "../lib/sparesults", features = ["rdf-star"] }
sparql-smith = { path = "../lib/sparql-smith" } sparql-smith = { path = "../lib/sparql-smith", features = ["sep-0006"] }
oxigraph = { path = "../lib" } oxigraph = { path = "../lib" }
[workspace] [workspace]

@ -39,7 +39,7 @@ lazy_static = "1"
sysinfo = "0.27" sysinfo = "0.27"
oxrdf = { version = "0.1.1", path="oxrdf", features = ["rdf-star", "oxsdatatypes"] } oxrdf = { version = "0.1.1", path="oxrdf", features = ["rdf-star", "oxsdatatypes"] }
oxsdatatypes = { version = "0.1.0", path="oxsdatatypes" } oxsdatatypes = { version = "0.1.0", path="oxsdatatypes" }
spargebra = { version = "0.2.3", path="spargebra", features = ["rdf-star"] } spargebra = { version = "0.2.3", path="spargebra", features = ["rdf-star", "sep-0006"] }
sparesults = { version = "0.1.3", path="sparesults", features = ["rdf-star"] } sparesults = { version = "0.1.3", path="sparesults", features = ["rdf-star"] }
[target.'cfg(not(target_arch = "wasm32"))'.dependencies] [target.'cfg(not(target_arch = "wasm32"))'.dependencies]

@ -16,6 +16,7 @@ rust-version = "1.60"
[features] [features]
default = [] default = []
rdf-star = ["oxrdf/rdf-star"] rdf-star = ["oxrdf/rdf-star"]
sep-0006 = []
[dependencies] [dependencies]
peg = "0.8" peg = "0.8"

@ -527,6 +527,9 @@ pub enum GraphPattern {
right: Box<Self>, right: Box<Self>,
expression: Option<Expression>, expression: Option<Expression>,
}, },
/// Lateral join i.e. evaluate right for all result row of left
#[cfg(feature = "sep-0006")]
Lateral { left: Box<Self>, right: Box<Self> },
/// [Filter](https://www.w3.org/TR/sparql11-query/#defn_algFilter). /// [Filter](https://www.w3.org/TR/sparql11-query/#defn_algFilter).
Filter { expr: Expression, inner: Box<Self> }, Filter { expr: Expression, inner: Box<Self> },
/// [Union](https://www.w3.org/TR/sparql11-query/#defn_algUnion). /// [Union](https://www.w3.org/TR/sparql11-query/#defn_algUnion).
@ -629,6 +632,14 @@ impl GraphPattern {
} }
write!(f, ")") write!(f, ")")
} }
#[cfg(feature = "sep-0006")]
Self::Lateral { left, right } => {
write!(f, "(lateral ")?;
left.fmt_sse(f)?;
write!(f, " ")?;
right.fmt_sse(f)?;
write!(f, ")")
}
Self::Filter { expr, inner } => { Self::Filter { expr, inner } => {
write!(f, "(filter ")?; write!(f, "(filter ")?;
expr.fmt_sse(f)?; expr.fmt_sse(f)?;
@ -793,17 +804,19 @@ impl fmt::Display for GraphPattern {
object, object,
} => write!(f, "{subject} {path} {object} ."), } => write!(f, "{subject} {path} {object} ."),
Self::Join { left, right } => { Self::Join { left, right } => {
if matches!( match right.as_ref() {
right.as_ref(),
Self::LeftJoin { .. } Self::LeftJoin { .. }
| Self::Minus { .. } | Self::Minus { .. }
| Self::Extend { .. } | Self::Extend { .. }
| Self::Filter { .. } | Self::Filter { .. } => {
) { // The second block might be considered as a modification of the first one.
// The second block might be considered as a modification of the first one. write!(f, "{left} {{ {right} }}")
write!(f, "{left} {{ {right} }}") }
} else { #[cfg(feature = "sep-0006")]
write!(f, "{left} {right}") Self::Lateral { .. } => {
write!(f, "{left} {{ {right} }}")
}
_ => write!(f, "{left} {right}"),
} }
} }
Self::LeftJoin { Self::LeftJoin {
@ -817,6 +830,10 @@ impl fmt::Display for GraphPattern {
write!(f, "{left} OPTIONAL {{ {right} }}") write!(f, "{left} OPTIONAL {{ {right} }}")
} }
} }
#[cfg(feature = "sep-0006")]
Self::Lateral { left, right } => {
write!(f, "{left} LATERAL {{ {right} }}")
}
Self::Filter { expr, inner } => { Self::Filter { expr, inner } => {
write!(f, "{inner} FILTER({expr})") write!(f, "{inner} FILTER({expr})")
} }
@ -940,6 +957,11 @@ impl GraphPattern {
left.lookup_in_scope_variables(callback); left.lookup_in_scope_variables(callback);
right.lookup_in_scope_variables(callback); right.lookup_in_scope_variables(callback);
} }
#[cfg(feature = "sep-0006")]
Self::Lateral { left, right } => {
left.lookup_in_scope_variables(callback);
right.lookup_in_scope_variables(callback);
}
Self::Graph { name, inner } => { Self::Graph { name, inner } => {
if let NamedNodePattern::Variable(ref g) = name { if let NamedNodePattern::Variable(ref g) = name {
callback(g); callback(g);

@ -360,6 +360,8 @@ impl<F, T: From<F>> From<FocusedTriplePattern<F>> for FocusedTripleOrPathPattern
#[derive(Eq, PartialEq, Debug, Clone, Hash)] #[derive(Eq, PartialEq, Debug, Clone, Hash)]
enum PartialGraphPattern { enum PartialGraphPattern {
Optional(GraphPattern, Option<Expression>), Optional(GraphPattern, Option<Expression>),
#[cfg(feature = "sep-0006")]
Lateral(GraphPattern),
Minus(GraphPattern), Minus(GraphPattern),
Bind(Expression, Variable), Bind(Expression, Variable),
Filter(Expression), Filter(Expression),
@ -620,6 +622,67 @@ fn are_variables_bound(expression: &Expression, variables: &HashSet<Variable>) -
} }
} }
/// Called on every variable defined using "AS" or "VALUES"
#[cfg(feature = "sep-0006")]
fn add_defined_variables<'a>(pattern: &'a GraphPattern, set: &mut HashSet<&'a Variable>) {
match pattern {
GraphPattern::Bgp { .. } | GraphPattern::Path { .. } => {}
GraphPattern::Join { left, right }
| GraphPattern::LeftJoin { left, right, .. }
| GraphPattern::Lateral { left, right }
| GraphPattern::Union { left, right }
| GraphPattern::Minus { left, right } => {
add_defined_variables(left, set);
add_defined_variables(right, set);
}
GraphPattern::Graph { inner, .. } => {
add_defined_variables(inner, set);
}
GraphPattern::Extend {
inner, variable, ..
} => {
set.insert(variable);
add_defined_variables(inner, set);
}
GraphPattern::Group {
variables,
aggregates,
inner,
} => {
for (v, _) in aggregates {
set.insert(v);
}
let mut inner_variables = HashSet::new();
add_defined_variables(inner, &mut inner_variables);
for v in inner_variables {
if variables.contains(v) {
set.insert(v);
}
}
}
GraphPattern::Values { variables, .. } => {
for v in variables {
set.insert(v);
}
}
GraphPattern::Project { variables, inner } => {
let mut inner_variables = HashSet::new();
add_defined_variables(inner, &mut inner_variables);
for v in inner_variables {
if variables.contains(v) {
set.insert(v);
}
}
}
GraphPattern::Service { inner, .. }
| GraphPattern::Filter { inner, .. }
| GraphPattern::OrderBy { inner, .. }
| GraphPattern::Distinct { inner }
| GraphPattern::Reduced { inner }
| GraphPattern::Slice { inner, .. } => add_defined_variables(inner, set),
}
}
fn copy_graph(from: impl Into<GraphName>, to: impl Into<GraphNamePattern>) -> GraphUpdateOperation { fn copy_graph(from: impl Into<GraphName>, to: impl Into<GraphNamePattern>) -> GraphUpdateOperation {
let bgp = GraphPattern::Bgp { let bgp = GraphPattern::Bgp {
patterns: vec![TriplePattern::new( patterns: vec![TriplePattern::new(
@ -1355,6 +1418,21 @@ parser! {
PartialGraphPattern::Optional(p, f) => { PartialGraphPattern::Optional(p, f) => {
g = GraphPattern::LeftJoin { left: Box::new(g), right: Box::new(p), expression: f } g = GraphPattern::LeftJoin { left: Box::new(g), right: Box::new(p), expression: f }
} }
#[cfg(feature = "sep-0006")]
PartialGraphPattern::Lateral(p) => {
let mut defined_variables = HashSet::default();
add_defined_variables(&p, &mut defined_variables);
let mut contains = false;
g.on_in_scope_variable(|v| {
if defined_variables.contains(v) {
contains = true;
}
});
if contains {
return Err("An existing variable is overriden in the right side of LATERAL");
}
g = GraphPattern::Lateral { left: Box::new(g), right: Box::new(p) }
}
PartialGraphPattern::Minus(p) => { PartialGraphPattern::Minus(p) => {
g = GraphPattern::Minus { left: Box::new(g), right: Box::new(p) } g = GraphPattern::Minus { left: Box::new(g), right: Box::new(p) }
} }
@ -1400,7 +1478,7 @@ parser! {
rule TriplesBlock_inner() -> Vec<TripleOrPathPattern> = _ h:TriplesSameSubjectPath() _ { h } rule TriplesBlock_inner() -> Vec<TripleOrPathPattern> = _ h:TriplesSameSubjectPath() _ { h }
//[56] //[56]
rule GraphPatternNotTriples() -> PartialGraphPattern = GroupOrUnionGraphPattern() / OptionalGraphPattern() / MinusGraphPattern() / GraphGraphPattern() / ServiceGraphPattern() / Filter() / Bind() / InlineData() rule GraphPatternNotTriples() -> PartialGraphPattern = GroupOrUnionGraphPattern() / OptionalGraphPattern() / LateralGraphPattern() / MinusGraphPattern() / GraphGraphPattern() / ServiceGraphPattern() / Filter() / Bind() / InlineData()
//[57] //[57]
rule OptionalGraphPattern() -> PartialGraphPattern = i("OPTIONAL") _ p:GroupGraphPattern() { rule OptionalGraphPattern() -> PartialGraphPattern = i("OPTIONAL") _ p:GroupGraphPattern() {
@ -1411,6 +1489,11 @@ parser! {
} }
} }
rule LateralGraphPattern() -> PartialGraphPattern = i("LATERAL") _ p:GroupGraphPattern() {?
#[cfg(feature = "sep-0006")]{Ok(PartialGraphPattern::Lateral(p))}
#[cfg(not(feature = "sep-0006"))]{Err("The LATERAL modifier is not supported")}
}
//[58] //[58]
rule GraphGraphPattern() -> PartialGraphPattern = i("GRAPH") _ name:VarOrIri() _ p:GroupGraphPattern() { rule GraphGraphPattern() -> PartialGraphPattern = i("GRAPH") _ name:VarOrIri() _ p:GroupGraphPattern() {
PartialGraphPattern::Other(GraphPattern::Graph { name, inner: Box::new(p) }) PartialGraphPattern::Other(GraphPattern::Graph { name, inner: Box::new(p) })

@ -12,5 +12,9 @@ A SPARQL test cases generator
""" """
edition = "2021" edition = "2021"
[features]
default = []
sep-0006 = []
[dependencies] [dependencies]
arbitrary = { version = "1", features = ["derive"] } arbitrary = { version = "1", features = ["derive"] }

@ -436,6 +436,8 @@ enum GraphPatternNotTriples {
Filter(Filter), Filter(Filter),
Bind(Bind), Bind(Bind),
InlineData(InlineData), // TODO: ServiceGraphPattern InlineData(InlineData), // TODO: ServiceGraphPattern
#[cfg(feature = "sep-0006")]
Lateral(LateralGraphPattern),
} }
impl fmt::Display for GraphPatternNotTriples { impl fmt::Display for GraphPatternNotTriples {
@ -448,6 +450,8 @@ impl fmt::Display for GraphPatternNotTriples {
Self::Filter(p) => write!(f, "{p}"), Self::Filter(p) => write!(f, "{p}"),
Self::Bind(p) => write!(f, "{p}"), Self::Bind(p) => write!(f, "{p}"),
Self::InlineData(p) => write!(f, "{p}"), Self::InlineData(p) => write!(f, "{p}"),
#[cfg(feature = "sep-0006")]
Self::Lateral(p) => write!(f, "{p}"),
} }
} }
} }
@ -464,6 +468,18 @@ impl fmt::Display for OptionalGraphPattern {
} }
} }
#[derive(Debug, Arbitrary)]
struct LateralGraphPattern {
// [] LateralGraphPattern ::= 'LATERAL' GroupGraphPattern
inner: GroupGraphPattern,
}
impl fmt::Display for LateralGraphPattern {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, " LATERAL {}", self.inner)
}
}
#[derive(Debug, Arbitrary)] #[derive(Debug, Arbitrary)]
struct GraphGraphPattern { struct GraphGraphPattern {
// [58] GraphGraphPattern ::= 'GRAPH' VarOrIri GroupGraphPattern // [58] GraphGraphPattern ::= 'GRAPH' VarOrIri GroupGraphPattern

@ -492,7 +492,8 @@ impl SimpleEvaluator {
current_right: Box::new(empty()), current_right: Box::new(empty()),
}) })
} else { } else {
Box::new(BadLeftJoinIterator { Box::new(BadForLoopLeftJoinIterator {
from_tuple: from.clone(),
right_evaluator: right.clone(), right_evaluator: right.clone(),
left_iter: left(from), left_iter: left(from),
current_left: None, current_left: None,
@ -635,32 +636,33 @@ impl SimpleEvaluator {
let mapping = mapping.clone(); let mapping = mapping.clone();
Rc::new(move |from| { Rc::new(move |from| {
let mapping = mapping.clone(); let mapping = mapping.clone();
Box::new( let mut input_tuple = EncodedTuple::with_capacity(mapping.len());
child(EncodedTuple::with_capacity(mapping.len())).filter_map( for (input_key, output_key) in mapping.iter() {
move |tuple| { if let Some(value) = from.get(*output_key) {
match tuple { input_tuple.set(*input_key, value.clone());
Ok(tuple) => { }
let mut output_tuple = from.clone(); }
for (input_key, output_key) in mapping.iter() { Box::new(child(input_tuple).filter_map(move |tuple| {
if let Some(value) = tuple.get(*input_key) { match tuple {
if let Some(existing_value) = Ok(tuple) => {
output_tuple.get(*output_key) let mut output_tuple = from.clone();
{ for (input_key, output_key) in mapping.iter() {
if existing_value != value { if let Some(value) = tuple.get(*input_key) {
return None; // Conflict if let Some(existing_value) = output_tuple.get(*output_key)
} {
} else { if existing_value != value {
output_tuple.set(*output_key, value.clone()); return None; // Conflict
}
} }
} else {
output_tuple.set(*output_key, value.clone());
} }
Some(Ok(output_tuple))
} }
Err(e) => Some(Err(e)),
} }
}, Some(Ok(output_tuple))
), }
) Err(e) => Some(Err(e)),
}
}))
}) })
} }
PlanNode::Aggregate { PlanNode::Aggregate {
@ -2943,27 +2945,6 @@ fn put_variable_value(
} }
} }
fn unbind_variables(binding: &mut EncodedTuple, variables: &[usize]) {
for var in variables {
binding.unset(*var)
}
}
fn combine_tuples(mut a: EncodedTuple, b: &EncodedTuple, vars: &[usize]) -> Option<EncodedTuple> {
for var in vars {
if let Some(b_value) = b.get(*var) {
if let Some(a_value) = a.get(*var) {
if a_value != b_value {
return None;
}
} else {
a.set(*var, b_value.clone());
}
}
}
Some(a)
}
pub fn are_compatible_and_not_disjointed(a: &EncodedTuple, b: &EncodedTuple) -> bool { pub fn are_compatible_and_not_disjointed(a: &EncodedTuple, b: &EncodedTuple) -> bool {
let mut found_intersection = false; let mut found_intersection = false;
for (a_value, b_value) in a.iter().zip(b.iter()) { for (a_value, b_value) in a.iter().zip(b.iter()) {
@ -3702,7 +3683,8 @@ impl Iterator for ForLoopLeftJoinIterator {
} }
} }
struct BadLeftJoinIterator { struct BadForLoopLeftJoinIterator {
from_tuple: EncodedTuple,
right_evaluator: Rc<dyn Fn(EncodedTuple) -> EncodedTuplesIterator>, right_evaluator: Rc<dyn Fn(EncodedTuple) -> EncodedTuplesIterator>,
left_iter: EncodedTuplesIterator, left_iter: EncodedTuplesIterator,
current_left: Option<EncodedTuple>, current_left: Option<EncodedTuple>,
@ -3710,18 +3692,16 @@ struct BadLeftJoinIterator {
problem_vars: Rc<Vec<usize>>, problem_vars: Rc<Vec<usize>>,
} }
impl Iterator for BadLeftJoinIterator { impl Iterator for BadForLoopLeftJoinIterator {
type Item = Result<EncodedTuple, EvaluationError>; type Item = Result<EncodedTuple, EvaluationError>;
fn next(&mut self) -> Option<Result<EncodedTuple, EvaluationError>> { fn next(&mut self) -> Option<Result<EncodedTuple, EvaluationError>> {
for right_tuple in &mut self.current_right { for right_tuple in &mut self.current_right {
match right_tuple { match right_tuple {
Ok(right_tuple) => { Ok(right_tuple) => {
if let Some(combined) = combine_tuples( if let Some(combined) =
right_tuple, right_tuple.combine_with(self.current_left.as_ref().unwrap())
self.current_left.as_ref().unwrap(), {
&self.problem_vars,
) {
return Some(Ok(combined)); return Some(Ok(combined));
} }
} }
@ -3730,15 +3710,19 @@ impl Iterator for BadLeftJoinIterator {
} }
match self.left_iter.next()? { match self.left_iter.next()? {
Ok(left_tuple) => { Ok(left_tuple) => {
let mut filtered_left = left_tuple.clone(); let mut right_input = self.from_tuple.clone();
unbind_variables(&mut filtered_left, &self.problem_vars); for (var, val) in left_tuple.iter().enumerate() {
self.current_right = (self.right_evaluator)(filtered_left); if let Some(val) = val {
if !self.problem_vars.contains(&var) {
right_input.set(var, val);
}
}
}
self.current_right = (self.right_evaluator)(right_input);
for right_tuple in &mut self.current_right { for right_tuple in &mut self.current_right {
match right_tuple { match right_tuple {
Ok(right_tuple) => { Ok(right_tuple) => {
if let Some(combined) = if let Some(combined) = right_tuple.combine_with(&left_tuple) {
combine_tuples(right_tuple, &left_tuple, &self.problem_vars)
{
self.current_left = Some(left_tuple); self.current_left = Some(left_tuple);
return Some(Ok(combined)); return Some(Ok(combined));
} }

@ -674,12 +674,6 @@ impl EncodedTuple {
self.inner[index] = Some(value); self.inner[index] = Some(value);
} }
pub fn unset(&mut self, index: usize) {
if let Some(v) = self.inner.get_mut(index) {
*v = None;
}
}
pub fn combine_with(&self, other: &Self) -> Option<Self> { pub fn combine_with(&self, other: &Self) -> Option<Self> {
if self.inner.len() < other.inner.len() { if self.inner.len() < other.inner.len() {
let mut result = other.inner.clone(); let mut result = other.inner.clone();

@ -133,6 +133,10 @@ impl<'a> PlanBuilder<'a> {
} }
} }
} }
GraphPattern::Lateral { left, right } => PlanNode::ForLoopJoin {
left: Box::new(self.build_for_graph_pattern(left, variables, graph_name)?),
right: Box::new(self.build_for_graph_pattern(right, variables, graph_name)?),
},
GraphPattern::Filter { expr, inner } => self.push_filter( GraphPattern::Filter { expr, inner } => self.push_filter(
Box::new(self.build_for_graph_pattern(inner, variables, graph_name)?), Box::new(self.build_for_graph_pattern(inner, variables, graph_name)?),
Box::new(self.build_for_expression(expr, variables, graph_name)?), Box::new(self.build_for_expression(expr, variables, graph_name)?),
@ -1120,8 +1124,11 @@ impl<'a> PlanBuilder<'a> {
right.lookup_used_variables(&mut |v| { right.lookup_used_variables(&mut |v| {
set.insert(v); set.insert(v);
}); });
let always_already_bound = left.always_bound_variables();
expression.lookup_used_variables(&mut |v| { expression.lookup_used_variables(&mut |v| {
set.insert(v); if !always_already_bound.contains(&v) {
set.insert(v);
}
}); });
} }
PlanNode::Extend { PlanNode::Extend {
@ -1138,10 +1145,14 @@ impl<'a> PlanBuilder<'a> {
} }
PlanNode::Sort { child, .. } PlanNode::Sort { child, .. }
| PlanNode::HashDeduplicate { child } | PlanNode::HashDeduplicate { child }
| PlanNode::Reduced { child } | PlanNode::Reduced { child } => {
| PlanNode::Skip { child, .. } Self::add_left_join_problematic_variables(child, set);
| PlanNode::Limit { child, .. } => { }
Self::add_left_join_problematic_variables(child, set) PlanNode::Skip { child, .. } | PlanNode::Limit { child, .. } => {
// Any variable might affect arity
child.lookup_used_variables(&mut |v| {
set.insert(v);
})
} }
PlanNode::Service { child, silent, .. } => { PlanNode::Service { child, silent, .. } => {
if *silent { if *silent {

@ -0,0 +1,5 @@
@prefix ex: <http://example.org/> .
ex:s1 a ex:T ; ex:p 11 , 12 , 13 .
ex:s2 a ex:T ; ex:p 21 , 22 , 23 .
ex:s3 a ex:T .

@ -0,0 +1,6 @@
PREFIX ex: <http://example.org/>
SELECT ?s ?o WHERE {
VALUES ?s { ex:S }
LATERAL { VALUES ?o { ex:O } FILTER(BOUND(?s) && EXISTS { FILTER(BOUND(?s)) }) }
}

@ -0,0 +1,6 @@
PREFIX ex: <http://example.org/>
SELECT ?s ?o WHERE {
VALUES ?s { ex:S }
LATERAL { GRAPH ex:G { FILTER(BOUND(?s)) . VALUES ?o { ex:O } } }
}

@ -0,0 +1,9 @@
PREFIX ex: <http://example.org/>
SELECT ?s ?o WHERE {
VALUES ?s { ex:S }
LATERAL {
{ VALUES ?o { ex:O } }
{ FILTER(BOUND(?s) && !BOUND(?o)) }
}
}

@ -0,0 +1,68 @@
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix : <https://github.com/oxigraph/oxigraph/tests/sparql/lateral/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 ut: <http://www.w3.org/2009/sparql/tests/test-update#> .
<> rdf:type mf:Manifest ;
rdfs:label "Oxigraph LATERAL feature SPARQL tests" ;
mf:entries
(
:subselect
:subselect_inside_optional
:subselect_outside_optional
:subselect_aggregate
:optional
:graph
:filter
:join
) .
:subselect rdf:type mf:QueryEvaluationTest ;
mf:name "Basic subselect LATERAL test" ;
mf:action
[ qt:query <subselect.rq> ;
qt:data <basic_input.ttl> ] ;
mf:result <subselect.srx> .
:subselect_inside_optional rdf:type mf:QueryEvaluationTest ;
mf:name "Basic subselect LATERAL test inside OPTIONAL" ;
mf:action
[ qt:query <subselect_inside_optional.rq> ;
qt:data <basic_input.ttl> ] ;
mf:result <subselect_inside_optional.srx> .
:subselect_outside_optional rdf:type mf:QueryEvaluationTest ;
mf:name "Basic subselect test inside LATERAL OPTIONAL" ;
mf:action
[ qt:query <subselect_outside_optional.rq> ;
qt:data <basic_input.ttl> ] ;
mf:result <subselect_outside_optional.srx> .
:subselect_aggregate rdf:type mf:QueryEvaluationTest ;
mf:name "LATERAL test with explicit aggregate" ;
mf:action
[ qt:query <subselect_aggregate.rq> ;
qt:data <basic_input.ttl> ] ;
mf:result <subselect_aggregate.srx> .
:optional rdf:type mf:QueryEvaluationTest ;
mf:name "LATERAL OPTIONAL test" ;
mf:action [ qt:query <optional.rq> ] ;
mf:result <simple.srx> .
:graph rdf:type mf:QueryEvaluationTest ;
mf:name "LATERAL GRAPH test" ;
mf:action [ qt:query <graph.rq> ] ;
mf:result <simple.srx> .
:filter rdf:type mf:QueryEvaluationTest ;
mf:name "LATERAL FILTER test" ;
mf:action [ qt:query <filter.rq> ] ;
mf:result <simple.srx> .
:join rdf:type mf:QueryEvaluationTest ;
mf:name "join in LATERAL test" ;
mf:action [ qt:query <join.rq> ] ;
mf:result <simple.srx> .

@ -0,0 +1,6 @@
PREFIX ex: <http://example.org/>
SELECT ?s ?o WHERE {
VALUES ?s { ex:S }
LATERAL { OPTIONAL { FILTER(BOUND(?s)) . VALUES ?o { ex:O } } }
}

@ -0,0 +1,17 @@
<?xml version="1.0"?>
<sparql xmlns="http://www.w3.org/2005/sparql-results#">
<head>
<variable name="s"/>
<variable name="o"/>
</head>
<results>
<result>
<binding name="s">
<uri>http://example.org/S</uri>
</binding>
<binding name="o">
<uri>http://example.org/O</uri>
</binding>
</result>
</results>
</sparql>

@ -0,0 +1,6 @@
PREFIX ex: <http://example.org/>
SELECT ?s ?o WHERE {
?s a ex:T.
LATERAL {SELECT ?s ?o WHERE { ?s ex:p ?o } ORDER BY ?o LIMIT 2}
}

@ -0,0 +1,41 @@
<?xml version="1.0"?>
<sparql xmlns="http://www.w3.org/2005/sparql-results#">
<head>
<variable name="s"/>
<variable name="o"/>
</head>
<results>
<result>
<binding name="s">
<uri>http://example.org/s1</uri>
</binding>
<binding name="o">
<literal datatype="http://www.w3.org/2001/XMLSchema#integer">11</literal>
</binding>
</result>
<result>
<binding name="s">
<uri>http://example.org/s1</uri>
</binding>
<binding name="o">
<literal datatype="http://www.w3.org/2001/XMLSchema#integer">12</literal>
</binding>
</result>
<result>
<binding name="s">
<uri>http://example.org/s2</uri>
</binding>
<binding name="o">
<literal datatype="http://www.w3.org/2001/XMLSchema#integer">21</literal>
</binding>
</result>
<result>
<binding name="s">
<uri>http://example.org/s2</uri>
</binding>
<binding name="o">
<literal datatype="http://www.w3.org/2001/XMLSchema#integer">22</literal>
</binding>
</result>
</results>
</sparql>

@ -0,0 +1,6 @@
PREFIX ex: <http://example.org/>
SELECT ?s ?c WHERE {
?s a ex:T.
LATERAL {SELECT ?s (MAX(?o) AS ?c) WHERE { ?s ex:p ?o } GROUP BY ?s}
}

@ -0,0 +1,25 @@
<?xml version="1.0"?>
<sparql xmlns="http://www.w3.org/2005/sparql-results#">
<head>
<variable name="s"/>
<variable name="c"/>
</head>
<results>
<result>
<binding name="s">
<uri>http://example.org/s1</uri>
</binding>
<binding name="c">
<literal datatype="http://www.w3.org/2001/XMLSchema#integer">13</literal>
</binding>
</result>
<result>
<binding name="s">
<uri>http://example.org/s2</uri>
</binding>
<binding name="c">
<literal datatype="http://www.w3.org/2001/XMLSchema#integer">23</literal>
</binding>
</result>
</results>
</sparql>

@ -0,0 +1,6 @@
PREFIX ex: <http://example.org/>
SELECT ?s ?o WHERE {
?s a ex:T.
OPTIONAL { LATERAL {SELECT ?s ?o WHERE { ?s ex:p ?o } ORDER BY ?o LIMIT 2} }
}

@ -0,0 +1,35 @@
<?xml version="1.0"?>
<sparql xmlns="http://www.w3.org/2005/sparql-results#">
<head>
<variable name="s"/>
<variable name="o"/>
</head>
<results>
<result>
<binding name="s">
<uri>http://example.org/s1</uri>
</binding>
<binding name="o">
<literal datatype="http://www.w3.org/2001/XMLSchema#integer">11</literal>
</binding>
</result>
<result>
<binding name="s">
<uri>http://example.org/s1</uri>
</binding>
<binding name="o">
<literal datatype="http://www.w3.org/2001/XMLSchema#integer">12</literal>
</binding>
</result>
<result>
<binding name="s">
<uri>http://example.org/s2</uri>
</binding>
</result>
<result>
<binding name="s">
<uri>http://example.org/s3</uri>
</binding>
</result>
</results>
</sparql>

@ -0,0 +1,6 @@
PREFIX ex: <http://example.org/>
SELECT ?s ?o WHERE {
?s a ex:T.
LATERAL { OPTIONAL {SELECT ?s ?o WHERE { ?s ex:p ?o } ORDER BY ?o LIMIT 2} }
}

@ -0,0 +1,46 @@
<?xml version="1.0"?>
<sparql xmlns="http://www.w3.org/2005/sparql-results#">
<head>
<variable name="s"/>
<variable name="o"/>
</head>
<results>
<result>
<binding name="s">
<uri>http://example.org/s1</uri>
</binding>
<binding name="o">
<literal datatype="http://www.w3.org/2001/XMLSchema#integer">11</literal>
</binding>
</result>
<result>
<binding name="s">
<uri>http://example.org/s1</uri>
</binding>
<binding name="o">
<literal datatype="http://www.w3.org/2001/XMLSchema#integer">12</literal>
</binding>
</result>
<result>
<binding name="s">
<uri>http://example.org/s2</uri>
</binding>
<binding name="o">
<literal datatype="http://www.w3.org/2001/XMLSchema#integer">21</literal>
</binding>
</result>
<result>
<binding name="s">
<uri>http://example.org/s2</uri>
</binding>
<binding name="o">
<literal datatype="http://www.w3.org/2001/XMLSchema#integer">22</literal>
</binding>
</result>
<result>
<binding name="s">
<uri>http://example.org/s3</uri>
</binding>
</result>
</results>
</sparql>

@ -7,6 +7,7 @@
<> rdf:type mf:Manifest ; <> rdf:type mf:Manifest ;
rdfs:label "Oxigraph SPARQL tests" ; rdfs:label "Oxigraph SPARQL tests" ;
mf:include ( <lateral/manifest.ttl> ) ;
mf:entries mf:entries
( (
:small_unicode_escape_with_multibytes_char :small_unicode_escape_with_multibytes_char

@ -16,7 +16,12 @@ fn run_testsuite(manifest_urls: Vec<&str>) -> Result<()> {
} }
} }
assert!(errors.is_empty(), "\n{}\n", errors.join("\n")); assert!(
errors.is_empty(),
"{} failing tests:\n{}\n",
errors.len(),
errors.join("\n")
);
Ok(()) Ok(())
} }

@ -16,7 +16,12 @@ fn run_testsuite(manifest_url: &str) -> Result<()> {
} }
} }
assert!(errors.is_empty(), "\n{}\n", errors.join("\n")); assert!(
errors.is_empty(),
"{} failing tests:\n{}\n",
errors.len(),
errors.join("\n")
);
Ok(()) Ok(())
} }

@ -18,7 +18,12 @@ fn run_testsuite(manifest_url: &str, ignored_tests: Vec<&str>) -> Result<()> {
} }
} }
assert!(errors.is_empty(), "\n{}\n", errors.join("\n")); assert!(
errors.is_empty(),
"{} failing tests:\n{}\n",
errors.len(),
errors.join("\n")
);
Ok(()) Ok(())
} }

Loading…
Cancel
Save