1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
//! # QueryParser - parsing Methods
//!
//! This module contains method implementations for `QueryParser`.
//!
//! ๐ค Generated with [SplitRS](https://github.com/cool-japan/splitrs)
use crate::algebra::{check_aggregate_arity, Expression, GroupCondition, OrderCondition};
use anyhow::Result;
use super::types::{Query, Token};
use super::queryparser_type::QueryParser;
/// Reject aggregate function calls with the wrong argument count inside a
/// `HAVING` condition at parse time.
///
/// `HAVING` is parsed by the generic expression grammar, which accepts any
/// argument count for a function call, so a malformed aggregate such as `SUM()`
/// or `COUNT(?a, ?b)` would otherwise parse cleanly and only fail deep in
/// execution โ surfacing to the HTTP layer as a 500 instead of a 400 parse
/// error. This walk mirrors the aggregate-hoisting recursion in the executor
/// (`rewrite_having_aggregates`): it descends `Function` / `Binary` / `Unary` /
/// `Conditional` shapes and validates each function call via the shared
/// [`check_aggregate_arity`] helper, so parser and executor reject identically.
/// The walk is scoped strictly to the `HAVING` condition.
fn validate_having_aggregate_arity(expr: &Expression) -> Result<()> {
match expr {
Expression::Function { name, args } => {
check_aggregate_arity(name, args.len()).map_err(|msg| anyhow::anyhow!(msg))?;
for arg in args {
validate_having_aggregate_arity(arg)?;
}
Ok(())
}
Expression::Binary { left, right, .. } => {
validate_having_aggregate_arity(left)?;
validate_having_aggregate_arity(right)
}
Expression::Unary { operand, .. } => validate_having_aggregate_arity(operand),
Expression::Conditional {
condition,
then_expr,
else_expr,
} => {
validate_having_aggregate_arity(condition)?;
validate_having_aggregate_arity(then_expr)?;
validate_having_aggregate_arity(else_expr)
}
_ => Ok(()),
}
}
impl QueryParser {
/// Parse a SPARQL query string into a Query AST
pub fn parse(&mut self, query_str: &str) -> Result<Query> {
self.tokenize(query_str)?;
self.parse_query()
}
pub(super) fn parse_solution_modifiers(&mut self, query: &mut Query) -> Result<()> {
if self.match_token(&Token::GroupBy) {
// The tokenizer emits `GROUP` as `Token::GroupBy` and the trailing
// `BY` as `Token::OrderBy`; swallow that stray keyword so the
// grouping expression list is read rather than mistaken for the end
// of the modifier (`is_solution_modifier_end` treats `OrderBy` as a
// terminator).
self.match_token(&Token::OrderBy);
while !self.is_at_end() && !self.is_solution_modifier_end() {
// A grouping condition is a bare `Var`, a `BuiltInCall` /
// `FunctionCall`, or the parenthesised `'(' Expression ('AS'
// Var)? ')'` form โ where the `AS` alias lives INSIDE the
// parentheses (`GROUP BY (LANG(?l) AS ?g)`).
let (expr, alias) = if matches!(self.peek(), Some(Token::LeftParen)) {
self.advance(); // consume '('
let expr = self.parse_expression()?;
let alias = if self.match_token(&Token::As) {
Some(self.expect_variable()?)
} else {
None
};
self.expect_token(Token::RightParen)?;
(expr, alias)
} else {
(self.parse_expression()?, None)
};
query.group_by.push(GroupCondition { expr, alias });
}
}
if self.match_token(&Token::Having) {
let having = self.parse_expression()?;
validate_having_aggregate_arity(&having)?;
query.having = Some(having);
}
if self.match_token(&Token::OrderBy) {
// `ORDER` and its trailing `BY` both tokenize to `Token::OrderBy`;
// swallow the second keyword before reading the order conditions.
self.match_token(&Token::OrderBy);
while !self.is_at_end() && !self.is_solution_modifier_end() {
let ascending = if self.match_token(&Token::Desc) {
false
} else {
self.match_token(&Token::Asc);
true
};
let expr = self.parse_expression()?;
query.order_by.push(OrderCondition { expr, ascending });
}
}
// `LimitOffsetClauses ::= LimitClause OffsetClause? | OffsetClause
// LimitClause?` (SPARQL 1.1 ยง18.5): BOTH orders are legal. A fixed
// LIMIT-then-OFFSET sequence silently drops the LIMIT of an
// `OFFSET n LIMIT m` tail โ the trailing `LIMIT` is never consumed, so
// the query returns every row past the offset instead of `m` rows (an
// HTTP-200 wrong answer). Read the two clauses in a loop that accepts
// whichever keyword comes next, in either order, until neither appears.
loop {
if self.match_token(&Token::Limit) {
query.limit = Some(self.parse_limit_offset_value("LIMIT")?);
} else if self.match_token(&Token::Offset) {
query.offset = Some(self.parse_limit_offset_value("OFFSET")?);
} else {
break;
}
}
Ok(())
}
/// Read the mandatory non-negative integer argument of a `LIMIT` / `OFFSET`
/// clause. A missing, non-numeric, non-integer or out-of-range value is a
/// parse error (surfaced as a 4xx) rather than being silently dropped โ
/// which would otherwise return every row instead of the intended cap.
fn parse_limit_offset_value(&mut self, keyword: &str) -> Result<usize> {
match self.peek() {
Some(Token::NumericLiteral(num)) => {
let num = num.clone();
let value = num.parse::<usize>().map_err(|_| {
anyhow::anyhow!("{keyword} requires a non-negative integer, got `{num}`")
})?;
self.advance();
Ok(value)
}
other => Err(anyhow::anyhow!(
"{keyword} requires an integer argument, got {other:?}"
)),
}
}
}