Implements SPARQL projection validation

pull/171/head
Tpt 3 years ago
parent a30081ab60
commit a7b48a0bda
  1. 1
      spargebra/src/algebra.rs
  2. 143
      spargebra/src/parser.rs
  3. 12
      testsuite/tests/sparql.rs

@ -636,6 +636,7 @@ impl Default for GraphPattern {
} }
impl GraphPattern { impl GraphPattern {
/// Returns the list of [in-scope variables](https://www.w3.org/TR/sparql11-query/#variableScope)
pub fn visible_variables(&self) -> BTreeSet<&Variable> { pub fn visible_variables(&self) -> BTreeSet<&Variable> {
let mut vars = BTreeSet::default(); let mut vars = BTreeSet::default();
self.add_visible_variables(&mut vars); self.add_visible_variables(&mut vars);

@ -438,8 +438,9 @@ fn build_select(
offset_limit: Option<(usize, Option<usize>)>, offset_limit: Option<(usize, Option<usize>)>,
values: Option<GraphPattern>, values: Option<GraphPattern>,
state: &mut ParserState, state: &mut ParserState,
) -> GraphPattern { ) -> Result<GraphPattern, &'static str> {
let mut p = wher; let mut p = wher;
let mut with_aggregate = false;
//GROUP BY //GROUP BY
let aggregates = state.aggregates.pop().unwrap_or_else(Vec::default); let aggregates = state.aggregates.pop().unwrap_or_else(Vec::default);
@ -471,6 +472,7 @@ fn build_select(
by: clauses, by: clauses,
aggregates, aggregates,
}; };
with_aggregate = true;
} }
//HAVING //HAVING
@ -487,31 +489,54 @@ fn build_select(
} }
//SELECT //SELECT
let mut pv: Vec<Variable> = Vec::new(); let mut pv = Vec::new();
match select.variables { match select.variables {
Some(sel_items) => { Some(sel_items) => {
let visible: HashSet<_> = p.visible_variables().into_iter().cloned().collect();
for sel_item in sel_items { for sel_item in sel_items {
match sel_item { let v = match sel_item {
SelectionMember::Variable(v) => pv.push(v), SelectionMember::Variable(v) => {
if with_aggregate && !visible.contains(&v) {
// We validate projection variables if there is an aggregate
return Err("The SELECT contains a variable that is unbound");
}
v
}
SelectionMember::Expression(expr, v) => { SelectionMember::Expression(expr, v) => {
if pv.contains(&v) { if visible.contains(&v) {
//TODO: fail // We disallow to override an existing variable with an expression
} else { return Err(
p = GraphPattern::Extend { "The SELECT overrides an existing variable using an expression",
inner: Box::new(p), );
var: v.clone(),
expr,
};
pv.push(v);
} }
if with_aggregate && !are_variables_bound(&expr, &visible) {
// We validate projection variables if there is an aggregate
return Err(
"The SELECT contains an exprssion with a variable that is unbound",
);
}
p = GraphPattern::Extend {
inner: Box::new(p),
var: v.clone(),
expr,
};
v
} }
};
if pv.contains(&v) {
return Err("Duplicated variable name in SELECT");
} }
pv.push(v)
} }
} }
None => { None => {
if with_aggregate {
return Err("SELECT * is not authorized with GROUP BY");
}
pv.extend(p.visible_variables().into_iter().cloned()) //TODO: is it really useful to do a projection? pv.extend(p.visible_variables().into_iter().cloned()) //TODO: is it really useful to do a projection?
} }
} }
let mut m = p; let mut m = p;
//ORDER BY //ORDER BY
@ -541,7 +566,47 @@ fn build_select(
length, length,
} }
} }
m Ok(m)
}
fn are_variables_bound(expression: &Expression, variables: &HashSet<Variable>) -> bool {
match expression {
Expression::NamedNode(_)
| Expression::Literal(_)
| Expression::Bound(_)
| Expression::Coalesce(_)
| Expression::Exists(_) => true,
Expression::Variable(var) => variables.contains(var),
Expression::UnaryPlus(e) | Expression::UnaryMinus(e) | Expression::Not(e) => {
are_variables_bound(&e, variables)
}
Expression::Or(a, b)
| Expression::And(a, b)
| Expression::Equal(a, b)
| Expression::SameTerm(a, b)
| Expression::Greater(a, b)
| Expression::GreaterOrEqual(a, b)
| Expression::Less(a, b)
| Expression::LessOrEqual(a, b)
| Expression::Add(a, b)
| Expression::Subtract(a, b)
| Expression::Multiply(a, b)
| Expression::Divide(a, b) => {
are_variables_bound(&a, variables) && are_variables_bound(&b, variables)
}
Expression::In(a, b) => {
are_variables_bound(&a, variables)
&& b.iter().all(|b| are_variables_bound(b, variables))
}
Expression::FunctionCall(_, parameters) => {
parameters.iter().all(|p| are_variables_bound(p, variables))
}
Expression::If(a, b, c) => {
are_variables_bound(&a, variables)
&& are_variables_bound(&b, variables)
&& are_variables_bound(&c, variables)
}
}
} }
fn copy_graph(from: impl Into<GraphName>, to: impl Into<GraphNamePattern>) -> GraphUpdateOperation { fn copy_graph(from: impl Into<GraphName>, to: impl Into<GraphNamePattern>) -> GraphUpdateOperation {
@ -853,16 +918,16 @@ parser! {
} }
//[7] //[7]
rule SelectQuery() -> Query = s:SelectClause() _ d:DatasetClauses() _ w:WhereClause() _ g:GroupClause()? _ h:HavingClause()? _ o:OrderClause()? _ l:LimitOffsetClauses()? _ v:ValuesClause() { rule SelectQuery() -> Query = s:SelectClause() _ d:DatasetClauses() _ w:WhereClause() _ g:GroupClause()? _ h:HavingClause()? _ o:OrderClause()? _ l:LimitOffsetClauses()? _ v:ValuesClause() {?
Query::Select { Ok(Query::Select {
dataset: d, dataset: d,
pattern: build_select(s, w, g, h, o, l, v, state), pattern: build_select(s, w, g, h, o, l, v, state)?,
base_iri: state.base_iri.clone() base_iri: state.base_iri.clone()
} })
} }
//[8] //[8]
rule SubSelect() -> GraphPattern = s:SelectClause() _ w:WhereClause() _ g:GroupClause()? _ h:HavingClause()? _ o:OrderClause()? _ l:LimitOffsetClauses()? _ v:ValuesClause() { rule SubSelect() -> GraphPattern = s:SelectClause() _ w:WhereClause() _ g:GroupClause()? _ h:HavingClause()? _ o:OrderClause()? _ l:LimitOffsetClauses()? _ v:ValuesClause() {?
build_select(s, w, g, h, o, l, v, state) build_select(s, w, g, h, o, l, v, state)
} }
@ -889,40 +954,40 @@ parser! {
//[10] //[10]
rule ConstructQuery() -> Query = rule ConstructQuery() -> Query =
i("CONSTRUCT") _ c:ConstructTemplate() _ d:DatasetClauses() _ w:WhereClause() _ g:GroupClause()? _ h:HavingClause()? _ o:OrderClause()? _ l:LimitOffsetClauses()? _ v:ValuesClause() { i("CONSTRUCT") _ c:ConstructTemplate() _ d:DatasetClauses() _ w:WhereClause() _ g:GroupClause()? _ h:HavingClause()? _ o:OrderClause()? _ l:LimitOffsetClauses()? _ v:ValuesClause() {?
Query::Construct { Ok(Query::Construct {
template: c, template: c,
dataset: d, dataset: d,
pattern: build_select(Selection::default(), w, g, h, o, l, v, state), pattern: build_select(Selection::default(), w, g, h, o, l, v, state)?,
base_iri: state.base_iri.clone() base_iri: state.base_iri.clone()
} })
} / } /
i("CONSTRUCT") _ d:DatasetClauses() _ i("WHERE") _ "{" _ c:ConstructQuery_optional_triple_template() _ "}" _ g:GroupClause()? _ h:HavingClause()? _ o:OrderClause()? _ l:LimitOffsetClauses()? _ v:ValuesClause() { i("CONSTRUCT") _ d:DatasetClauses() _ i("WHERE") _ "{" _ c:ConstructQuery_optional_triple_template() _ "}" _ g:GroupClause()? _ h:HavingClause()? _ o:OrderClause()? _ l:LimitOffsetClauses()? _ v:ValuesClause() {?
Query::Construct { Ok(Query::Construct {
template: c.clone(), template: c.clone(),
dataset: d, dataset: d,
pattern: build_select( pattern: build_select(
Selection::default(), Selection::default(),
GraphPattern::Bgp(c), GraphPattern::Bgp(c),
g, h, o, l, v, state g, h, o, l, v, state
), )?,
base_iri: state.base_iri.clone() base_iri: state.base_iri.clone()
} })
} }
rule ConstructQuery_optional_triple_template() -> Vec<TriplePattern> = TriplesTemplate() / { Vec::new() } rule ConstructQuery_optional_triple_template() -> Vec<TriplePattern> = TriplesTemplate() / { Vec::new() }
//[11] //[11]
rule DescribeQuery() -> Query = rule DescribeQuery() -> Query =
i("DESCRIBE") _ "*" _ d:DatasetClauses() w:WhereClause()? _ g:GroupClause()? _ h:HavingClause()? _ o:OrderClause()? _ l:LimitOffsetClauses()? _ v:ValuesClause() { i("DESCRIBE") _ "*" _ d:DatasetClauses() w:WhereClause()? _ g:GroupClause()? _ h:HavingClause()? _ o:OrderClause()? _ l:LimitOffsetClauses()? _ v:ValuesClause() {?
Query::Describe { Ok(Query::Describe {
dataset: d, dataset: d,
pattern: build_select(Selection::default(), w.unwrap_or_else(GraphPattern::default), g, h, o, l, v, state), pattern: build_select(Selection::default(), w.unwrap_or_else(GraphPattern::default), g, h, o, l, v, state)?,
base_iri: state.base_iri.clone() base_iri: state.base_iri.clone()
} })
} / } /
i("DESCRIBE") _ p:DescribeQuery_item()+ _ d:DatasetClauses() w:WhereClause()? _ g:GroupClause()? _ h:HavingClause()? _ o:OrderClause()? _ l:LimitOffsetClauses()? _ v:ValuesClause() { i("DESCRIBE") _ p:DescribeQuery_item()+ _ d:DatasetClauses() w:WhereClause()? _ g:GroupClause()? _ h:HavingClause()? _ o:OrderClause()? _ l:LimitOffsetClauses()? _ v:ValuesClause() {?
Query::Describe { Ok(Query::Describe {
dataset: d, dataset: d,
pattern: build_select(Selection { pattern: build_select(Selection {
option: SelectionOption::Default, option: SelectionOption::Default,
@ -930,19 +995,19 @@ parser! {
NamedNodePattern::NamedNode(n) => SelectionMember::Expression(n.into(), variable()), NamedNodePattern::NamedNode(n) => SelectionMember::Expression(n.into(), variable()),
NamedNodePattern::Variable(v) => SelectionMember::Variable(v) NamedNodePattern::Variable(v) => SelectionMember::Variable(v)
}).collect()) }).collect())
}, w.unwrap_or_else(GraphPattern::default), g, h, o, l, v, state), }, w.unwrap_or_else(GraphPattern::default), g, h, o, l, v, state)?,
base_iri: state.base_iri.clone() base_iri: state.base_iri.clone()
} })
} }
rule DescribeQuery_item() -> NamedNodePattern = i:VarOrIri() _ { i } rule DescribeQuery_item() -> NamedNodePattern = i:VarOrIri() _ { i }
//[12] //[12]
rule AskQuery() -> Query = i("ASK") _ d:DatasetClauses() w:WhereClause() _ g:GroupClause()? _ h:HavingClause()? _ o:OrderClause()? _ l:LimitOffsetClauses()? _ v:ValuesClause() { rule AskQuery() -> Query = i("ASK") _ d:DatasetClauses() w:WhereClause() _ g:GroupClause()? _ h:HavingClause()? _ o:OrderClause()? _ l:LimitOffsetClauses()? _ v:ValuesClause() {?
Query::Ask { Ok(Query::Ask {
dataset: d, dataset: d,
pattern: build_select(Selection::default(), w, g, h, o, l, v, state), pattern: build_select(Selection::default(), w, g, h, o, l, v, state)?,
base_iri: state.base_iri.clone() base_iri: state.base_iri.clone()
} })
} }
//[13] //[13]

@ -70,21 +70,9 @@ fn sparql11_query_w3c_evaluation_testsuite() -> Result<()> {
"http://www.w3.org/2009/sparql/docs/tests/data-sparql11/manifest-sparql11-query.ttl", "http://www.w3.org/2009/sparql/docs/tests/data-sparql11/manifest-sparql11-query.ttl",
vec![ vec![
//Bad SPARQL query that should be rejected by the parser //Bad SPARQL query that should be rejected by the parser
"http://www.w3.org/2009/sparql/docs/tests/data-sparql11/aggregates/manifest#agg08",
"http://www.w3.org/2009/sparql/docs/tests/data-sparql11/aggregates/manifest#agg09",
"http://www.w3.org/2009/sparql/docs/tests/data-sparql11/aggregates/manifest#agg10",
"http://www.w3.org/2009/sparql/docs/tests/data-sparql11/aggregates/manifest#agg11",
"http://www.w3.org/2009/sparql/docs/tests/data-sparql11/aggregates/manifest#agg12",
"http://www.w3.org/2009/sparql/docs/tests/data-sparql11/grouping/manifest#group07",
"http://www.w3.org/2009/sparql/docs/tests/data-sparql11/grouping/manifest#group06",
"http://www.w3.org/2009/sparql/docs/tests/data-sparql11/grouping/manifest#group07",
"http://www.w3.org/2009/sparql/docs/tests/data-sparql11/syntax-query/manifest#test_43",
"http://www.w3.org/2009/sparql/docs/tests/data-sparql11/syntax-query/manifest#test_44",
"http://www.w3.org/2009/sparql/docs/tests/data-sparql11/syntax-query/manifest#test_45",
"http://www.w3.org/2009/sparql/docs/tests/data-sparql11/syntax-query/manifest#test_60", "http://www.w3.org/2009/sparql/docs/tests/data-sparql11/syntax-query/manifest#test_60",
"http://www.w3.org/2009/sparql/docs/tests/data-sparql11/syntax-query/manifest#test_61a", "http://www.w3.org/2009/sparql/docs/tests/data-sparql11/syntax-query/manifest#test_61a",
"http://www.w3.org/2009/sparql/docs/tests/data-sparql11/syntax-query/manifest#test_62a", "http://www.w3.org/2009/sparql/docs/tests/data-sparql11/syntax-query/manifest#test_62a",
"http://www.w3.org/2009/sparql/docs/tests/data-sparql11/syntax-query/manifest#test_65",
//BNODE() scope is currently wrong //BNODE() scope is currently wrong
"http://www.w3.org/2009/sparql/docs/tests/data-sparql11/functions/manifest#bnode01", "http://www.w3.org/2009/sparql/docs/tests/data-sparql11/functions/manifest#bnode01",
//Property path with unbound graph name are not supported yet //Property path with unbound graph name are not supported yet

Loading…
Cancel
Save