Skip to main content

linkrs_query/parser/parsing/
clause_parser.rs

1//! Sentence Parsing Module
2//!
3//! Responsible for parsing various shared clauses, including RETURN, YIELD, SET, OVER, WHERE, etc.
4
5use crate::parser::ast::stmt::*;
6use crate::parser::ast::types::{LimitClause, OrderDirection, SkipClause};
7use crate::parser::core::error::{ParseError, ParseErrorKind};
8use crate::parser::parsing::expr_parser::parse_expression_with_context;
9use crate::parser::parsing::parse_context::ParseContext;
10use crate::parser::TokenKind;
11use linkrs_core::types::expr::contextual::ContextualExpression;
12use linkrs_core::types::expr::Expression as CoreExpression;
13use linkrs_core::types::graph_schema::EdgeDirection;
14
15/// Sentence parser
16pub struct ClauseParser;
17
18impl ClauseParser {
19    pub fn new() -> Self {
20        Self
21    }
22
23    /// Analysis of the RETURN clause
24    pub fn parse_return_clause(
25        &mut self,
26        ctx: &mut ParseContext,
27    ) -> Result<ReturnClause, ParseError> {
28        let span = ctx.current_span();
29
30        let distinct = ctx.match_token(TokenKind::Distinct);
31
32        let mut items = Vec::new();
33
34        // Check whether it is *
35        if ctx.match_token(TokenKind::Star) {
36            let expr = CoreExpression::variable("*");
37            let expr_meta = linkrs_core::types::expr::ExpressionMeta::new(expr);
38            let id = ctx.expression_context().register_expression(expr_meta);
39            let ctx_expr = ContextualExpression::new(id, ctx.expression_context_clone());
40            items.push(ReturnItem::Expression {
41                expression: ctx_expr,
42                alias: None,
43            });
44        } else {
45            loop {
46                let Some(item) = ctx.recover_clause(
47                    |_| Ok(None),
48                    |c| {
49                        let expr = self.parse_expression(c)?;
50                        let alias = if c.match_token(TokenKind::As) {
51                            Some(c.expect_identifier()?)
52                        } else {
53                            None
54                        };
55                        Ok(Some(ReturnItem::Expression {
56                            expression: expr,
57                            alias,
58                        }))
59                    },
60                )?
61                else {
62                    break;
63                };
64                items.push(item);
65                if !ctx.match_token(TokenKind::Comma) {
66                    break;
67                }
68            }
69        }
70
71        // Explanation of `ORDER BY`
72        let mut order_by = if ctx.match_token(TokenKind::Order) {
73            ctx.recover_clause(
74                |_| Ok(None),
75                |c| {
76                    c.expect_token(TokenKind::By)?;
77                    self.parse_order_by_clause(c).map(Some)
78                },
79            )?
80        } else {
81            None
82        };
83
84        // Analysis of SKIP and LIMIT (support both orders: SKIP before LIMIT or LIMIT before SKIP)
85        let mut limit: Option<LimitClause> = None;
86        let mut skip: Option<SkipClause> = None;
87
88        // First, try to parse SKIP if present
89        if ctx.match_token(TokenKind::Skip) {
90            skip = ctx.recover_clause(|_| Ok(None), |c| self.parse_skip_clause(c))?;
91        }
92
93        // Then, try to parse LIMIT if present
94        if ctx.match_token(TokenKind::Limit) {
95            limit = ctx.recover_clause(|_| Ok(None), |c| self.parse_limit_clause(c))?;
96        }
97
98        // If SKIP wasn't parsed yet, try again (handles LIMIT before SKIP case)
99        if skip.is_none() && ctx.match_token(TokenKind::Skip) {
100            skip = ctx.recover_clause(|_| Ok(None), |c| self.parse_skip_clause(c))?;
101        }
102
103        // Consume GROUP BY items if present (group keys are extracted from non-aggregate return columns)
104        // Supports ROLLUP, CUBE, and GROUPING SETS syntax
105        if ctx.match_token(TokenKind::Group) {
106            ctx.recover_clause(|_| Ok(()), |c| self.parse_return_group_by(c))?;
107        }
108
109        // Accept an ORDER BY clause after GROUP BY (`GROUP BY ... ORDER BY ...`).
110        if order_by.is_none() && ctx.match_token(TokenKind::Order) {
111            order_by = ctx.recover_clause(
112                |_| Ok(None),
113                |c| {
114                    c.expect_token(TokenKind::By)?;
115                    self.parse_order_by_clause(c).map(Some)
116                },
117            )?;
118        }
119
120        // Parse optional SAMPLE clause
121        let sample = if ctx.match_token(TokenKind::Sample) {
122            ctx.recover_clause(
123                |_| Ok(None),
124                |c| {
125                    let count = c.expect_integer_literal()? as usize;
126                    Ok(Some(crate::parser::ast::types::SampleClause {
127                        span: c.current_span(),
128                        count,
129                        percentage: None,
130                    }))
131                },
132            )?
133        } else {
134            None
135        };
136
137        // Parse optional HAVING clause
138        let having_clause = if ctx.match_token(TokenKind::Having) {
139            ctx.recover_clause(|_| Ok(None), |c| self.parse_expression(c).map(Some))?
140        } else {
141            None
142        };
143
144        Ok(ReturnClause {
145            span,
146            items,
147            distinct,
148            order_by,
149            limit,
150            skip,
151            sample,
152            having_clause,
153        })
154    }
155
156    /// Analyzing the YIELD clause
157    ///
158    /// Assuming that the YIELD token has been consumed by the caller, this method will only parse the subsequent list of expressions, as well as subqueries such as WHERE, LIMIT, and SKIP.
159    pub fn parse_yield_clause(
160        &mut self,
161        ctx: &mut ParseContext,
162    ) -> Result<YieldClause, ParseError> {
163        let start_span = ctx.current_span();
164
165        let distinct = ctx.match_token(TokenKind::Distinct);
166
167        let mut items = Vec::new();
168
169        // Check whether it is *.
170        if ctx.match_token(TokenKind::Star) {
171            // “YIELD *” indicates that all columns should be returned.
172        } else {
173            loop {
174                let Some(item) = ctx.recover_clause(
175                    |_| Ok(None),
176                    |c| {
177                        let expr = self.parse_expression(c)?;
178                        let alias = if c.match_token(TokenKind::As) {
179                            Some(c.expect_identifier()?)
180                        } else {
181                            None
182                        };
183                        Ok(Some(YieldItem {
184                            expression: expr,
185                            alias,
186                        }))
187                    },
188                )?
189                else {
190                    break;
191                };
192                items.push(item);
193                if !ctx.match_token(TokenKind::Comma) {
194                    break;
195                }
196            }
197        }
198
199        // Analyzing the WHERE clause
200        let where_clause = if ctx.match_token(TokenKind::Where) {
201            ctx.recover_clause(|_| Ok(None), |c| self.parse_expression(c).map(Some))?
202        } else {
203            None
204        };
205
206        // Explanation of `ORDER BY`
207        let order_by = if ctx.match_token(TokenKind::Order) {
208            ctx.recover_clause(
209                |_| Ok(None),
210                |c| {
211                    c.expect_token(TokenKind::By)?;
212                    self.parse_order_by_clause(c).map(Some)
213                },
214            )?
215        } else {
216            None
217        };
218
219        // Analysis of the LIMIT clause
220        let limit = if ctx.match_token(TokenKind::Limit) {
221            ctx.recover_clause(|_| Ok(None), |c| self.parse_limit_clause(c))?
222        } else {
223            None
224        };
225
226        // Analysis of SKIP
227        let skip = if ctx.match_token(TokenKind::Skip) {
228            ctx.recover_clause(|_| Ok(None), |c| self.parse_skip_clause(c))?
229        } else {
230            None
231        };
232
233        // Parse optional SAMPLE clause
234        let sample = if ctx.match_token(TokenKind::Sample) {
235            ctx.recover_clause(
236                |_| Ok(None),
237                |c| {
238                    let count = c.expect_integer_literal()? as usize;
239                    Ok(Some(crate::parser::ast::types::SampleClause {
240                        span: c.current_span(),
241                        count,
242                        percentage: None,
243                    }))
244                },
245            )?
246        } else {
247            None
248        };
249
250        let end_span = ctx.current_span();
251
252        Ok(YieldClause {
253            span: ctx.merge_span(start_span.start, end_span.end),
254            distinct,
255            items,
256            where_clause,
257            order_by,
258            limit,
259            skip,
260            sample,
261        })
262    }
263
264    /// Analyzing the SET clause
265    pub fn parse_set_clause(&mut self, ctx: &mut ParseContext) -> Result<SetClause, ParseError> {
266        let span = ctx.current_span();
267        let assignments = self.parse_set_assignments(ctx)?;
268        Ok(SetClause { span, assignments })
269    }
270
271    /// Analyzing the SET assignment list
272    pub fn parse_set_assignments(
273        &mut self,
274        ctx: &mut ParseContext,
275    ) -> Result<Vec<Assignment>, ParseError> {
276        let mut assignments = Vec::new();
277        loop {
278            let Some(assignment) =
279                ctx.recover_clause(|_| Ok(None), |c| self.parse_assignment(c).map(Some))?
280            else {
281                break;
282            };
283            assignments.push(assignment);
284            if !ctx.match_token(TokenKind::Comma) {
285                break;
286            }
287        }
288        Ok(assignments)
289    }
290
291    /// Parse a single SET assignment: `<property path> = <expression>`.
292    fn parse_assignment(&mut self, ctx: &mut ParseContext) -> Result<Assignment, ParseError> {
293        // Parse the LHS as a property path (not a full expression) so the
294        // assignment `=` is not consumed as an equality comparison.
295        let property_expr = crate::parser::parsing::expr_parser::parse_property_path_with_context(
296            ctx,
297            ctx.expression_context_clone(),
298        )?;
299        ctx.expect_token(TokenKind::Assign)?;
300        let value = self.parse_expression(ctx)?;
301
302        let (property, target, is_bare_variable) = match property_expr.expression() {
303            Some(expr) => match expr.inner() {
304                CoreExpression::Property { object, property } => {
305                    // Check if object is a literal (e.g., 1.age) or a variable (e.g., p.age)
306                    let target = match object.as_ref() {
307                        CoreExpression::Literal(_) => Some(property_expr.clone()),
308                        CoreExpression::Variable(_) => None, // Variable-based property access
309                        _ => Some(property_expr.clone()),
310                    };
311                    (property.clone(), target, false)
312                }
313                CoreExpression::Variable(name) => (name.clone(), None, true),
314                _ => {
315                    return Err(ParseError::new(
316                        ParseErrorKind::SyntaxError,
317                        "SET assignment requires a property path (e.g., p.age)".to_string(),
318                        ctx.current_position(),
319                    ));
320                }
321            },
322            None => {
323                return Err(ParseError::new(
324                    ParseErrorKind::SyntaxError,
325                    "Expression not registered in context".to_string(),
326                    ctx.current_position(),
327                ));
328            }
329        };
330
331        let is_map_overwrite = if !is_bare_variable {
332            false
333        } else if let Some(value_meta) = value.expression() {
334            matches!(value_meta.inner(), CoreExpression::Map(_))
335        } else {
336            false
337        };
338
339        Ok(Assignment {
340            property,
341            value,
342            target,
343            object: None,
344            is_map_overwrite,
345        })
346    }
347
348    /// Analysis of the OVER clause
349    pub fn parse_over_clause(&mut self, ctx: &mut ParseContext) -> Result<OverClause, ParseError> {
350        let span = ctx.current_span();
351
352        ctx.recover_clause(
353            |c| {
354                Ok(OverClause {
355                    span: c.current_span(),
356                    edge_types: Vec::new(),
357                    direction: EdgeDirection::Out,
358                })
359            },
360            |c| {
361                let edge_types = self.parse_edge_types(c)?;
362
363                // Analysis direction (optional)
364                let direction =
365                    if c.match_token(TokenKind::In) || c.match_token(TokenKind::Reversely) {
366                        EdgeDirection::In
367                    } else if c.match_token(TokenKind::Bidirect) {
368                        EdgeDirection::Both
369                    } else {
370                        EdgeDirection::Out
371                    };
372
373                Ok(OverClause {
374                    span,
375                    edge_types,
376                    direction,
377                })
378            },
379        )
380    }
381
382    /// Analyzing the list of edge types
383    fn parse_edge_types(&mut self, ctx: &mut ParseContext) -> Result<Vec<String>, ParseError> {
384        let mut types = Vec::new();
385        types.push(ctx.expect_identifier()?);
386        while ctx.match_token(TokenKind::Comma) {
387            types.push(ctx.expect_identifier()?);
388        }
389        Ok(types)
390    }
391
392    /// Parse a SKIP clause body (the SKIP token has already been consumed).
393    fn parse_skip_clause(
394        &mut self,
395        ctx: &mut ParseContext,
396    ) -> Result<Option<SkipClause>, ParseError> {
397        let count = ctx.expect_integer_literal()? as usize;
398        Ok(Some(SkipClause {
399            span: ctx.current_span(),
400            count,
401        }))
402    }
403
404    /// Parse a LIMIT clause body (the LIMIT token has already been consumed).
405    fn parse_limit_clause(
406        &mut self,
407        ctx: &mut ParseContext,
408    ) -> Result<Option<LimitClause>, ParseError> {
409        let count = ctx.expect_integer_literal()? as usize;
410        Ok(Some(LimitClause {
411            span: ctx.current_span(),
412            count,
413        }))
414    }
415
416    /// Parse the GROUP BY body of a RETURN clause (GROUP has been consumed).
417    /// Supports ROLLUP, CUBE, and GROUPING SETS syntax.
418    fn parse_return_group_by(&mut self, ctx: &mut ParseContext) -> Result<(), ParseError> {
419        ctx.expect_token(TokenKind::By)?;
420
421        if ctx.match_token(TokenKind::Rollup) {
422            // GROUP BY ROLLUP(...)
423            ctx.expect_token(TokenKind::LParen)?;
424            loop {
425                self.parse_expression(ctx)?;
426                if !ctx.match_token(TokenKind::Comma) {
427                    break;
428                }
429            }
430            ctx.expect_token(TokenKind::RParen)?;
431        } else if ctx.match_token(TokenKind::Cube) {
432            // GROUP BY CUBE(...)
433            ctx.expect_token(TokenKind::LParen)?;
434            loop {
435                self.parse_expression(ctx)?;
436                if !ctx.match_token(TokenKind::Comma) {
437                    break;
438                }
439            }
440            ctx.expect_token(TokenKind::RParen)?;
441        } else if ctx.match_token(TokenKind::Grouping) {
442            // GROUP BY GROUPING SETS((...), (...))
443            ctx.expect_token(TokenKind::Sets)?;
444            ctx.expect_token(TokenKind::LParen)?;
445            loop {
446                ctx.expect_token(TokenKind::LParen)?;
447                loop {
448                    self.parse_expression(ctx)?;
449                    if !ctx.match_token(TokenKind::Comma) {
450                        break;
451                    }
452                }
453                ctx.expect_token(TokenKind::RParen)?;
454                if !ctx.match_token(TokenKind::Comma) {
455                    break;
456                }
457            }
458            ctx.expect_token(TokenKind::RParen)?;
459        } else {
460            // Standard GROUP BY expr, expr, ...
461            loop {
462                self.parse_expression(ctx)?;
463                if !ctx.match_token(TokenKind::Comma) {
464                    break;
465                }
466            }
467        }
468        Ok(())
469    }
470
471    /// Analyzing the ORDER BY clause
472    fn parse_order_by_clause(
473        &mut self,
474        ctx: &mut ParseContext,
475    ) -> Result<OrderByClause, ParseError> {
476        let span = ctx.current_span();
477        let mut items = Vec::new();
478
479        loop {
480            let expr = self.parse_expression(ctx)?;
481            let direction = if ctx.match_token(TokenKind::Asc) {
482                OrderDirection::Asc
483            } else if ctx.match_token(TokenKind::Desc) {
484                OrderDirection::Desc
485            } else {
486                OrderDirection::Asc
487            };
488            items.push(OrderByItem {
489                expression: expr,
490                direction,
491            });
492            if !ctx.match_token(TokenKind::Comma) {
493                break;
494            }
495        }
496
497        Ok(OrderByClause { span, items })
498    }
499
500    /// Analyzing the expression
501    fn parse_expression(
502        &mut self,
503        ctx: &mut ParseContext,
504    ) -> Result<ContextualExpression, ParseError> {
505        parse_expression_with_context(ctx, ctx.expression_context_clone())
506    }
507}
508
509impl Default for ClauseParser {
510    fn default() -> Self {
511        Self::new()
512    }
513}