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