Parse function call: name(args...) using ISO GQL compliant token-based parsing
(tokens: &[Token])
| 2120 | |
| 2121 | /// Parse function call: name(args...) using ISO GQL compliant token-based parsing |
| 2122 | fn function_call(tokens: &[Token]) -> IResult<&[Token], FunctionCall> { |
| 2123 | // ISO GQL: <function-call> ::= <identifier> "(" [DISTINCT|ALL] [<expression> ("," <expression>)*] ")" |
| 2124 | |
| 2125 | // Parse function name (identifier) |
| 2126 | let (tokens, name) = identifier(tokens)?; |
| 2127 | let name = name.to_uppercase(); // Normalize function names to uppercase |
| 2128 | |
| 2129 | // Parse opening parenthesis |
| 2130 | let (tokens, _) = expect_token(Token::LeftParen)(tokens)?; |
| 2131 | |
| 2132 | // Parse optional DISTINCT/ALL qualifier for aggregate functions |
| 2133 | let (tokens, distinct_qualifier) = opt(distinct_qualifier)(tokens)?; |
| 2134 | let distinct = distinct_qualifier.unwrap_or(DistinctQualifier::None); |
| 2135 | |
| 2136 | // Parse arguments (expressions separated by commas) |
| 2137 | let mut arguments = Vec::new(); |
| 2138 | let mut remaining = tokens; |
| 2139 | |
| 2140 | // Check if there are arguments (not immediate closing paren) |
| 2141 | if !matches!(remaining.first(), Some(Token::RightParen)) { |
| 2142 | // Special case for COUNT(*) and similar aggregate functions with * |
| 2143 | if matches!(remaining.first(), Some(Token::Star)) { |
| 2144 | // Create a wildcard variable expression for * |
| 2145 | arguments.push(Expression::Variable(Variable { |
| 2146 | name: "*".to_string(), |
| 2147 | location: Location::default(), |
| 2148 | })); |
| 2149 | remaining = &remaining[1..]; // consume the star |
| 2150 | } else { |
| 2151 | // Regular argument parsing loop |
| 2152 | loop { |
| 2153 | // Parse an expression as argument |
| 2154 | let (new_remaining, expr) = expression(remaining)?; |
| 2155 | arguments.push(expr); |
| 2156 | remaining = new_remaining; |
| 2157 | |
| 2158 | // Check for comma (more arguments) or closing paren (end) |
| 2159 | match remaining.first() { |
| 2160 | Some(Token::Comma) => { |
| 2161 | remaining = &remaining[1..]; // consume comma |
| 2162 | continue; |
| 2163 | } |
| 2164 | Some(Token::RightParen) => break, |
| 2165 | _ => { |
| 2166 | return Err(nom::Err::Error(nom::error::Error::new( |
| 2167 | remaining, |
| 2168 | nom::error::ErrorKind::Tag, |
| 2169 | ))) |
| 2170 | } |
| 2171 | } |
| 2172 | } |
| 2173 | } |
| 2174 | } |
| 2175 | |
| 2176 | // Parse closing parenthesis |
| 2177 | let (remaining, _) = expect_token(Token::RightParen)(remaining)?; |
| 2178 | |
| 2179 | Ok(( |
nothing calls this directly
no test coverage detected