Skip to main content

linkrs_query/parser/parsing/traversal_parser/
match.rs

1use crate::parser::ast::hint::JoinHintAst;
2use crate::parser::ast::pattern::Pattern;
3use crate::parser::ast::stmt::*;
4use crate::parser::core::error::{ParseError, ParseErrorKind};
5use crate::parser::parsing::clause_parser::ClauseParser;
6use crate::parser::parsing::parse_context::ParseContext;
7use crate::parser::TokenKind;
8
9use super::{ParsedMatchClause, TraversalParser};
10
11impl TraversalParser {
12    /// Analyzing the MATCH statement
13    pub fn parse_match_statement(&mut self, ctx: &mut ParseContext) -> Result<Stmt, ParseError> {
14        let start_span = ctx.current_span();
15
16        let mut optional = ctx.match_token(TokenKind::Optional);
17
18        ctx.expect_token(TokenKind::Match)?;
19
20        let first = self.parse_match_clause(ctx)?;
21        let mut patterns = first.patterns;
22        let mut join_hint = first.join_hint;
23        let mut where_clause = first.where_clause;
24        let mut where_explicit = first.where_explicit;
25        let mut return_clause = first.return_clause;
26        let mut delete_clause = first.delete_clause;
27
28        while !optional
29            && return_clause.is_none()
30            && delete_clause.is_none()
31            && (ctx.check_token(TokenKind::Match)
32                || (ctx.check_token(TokenKind::Optional)
33                    && ctx.peek_token().kind == TokenKind::Match))
34        {
35            if ctx.check_token(TokenKind::Optional) {
36                ctx.expect_token(TokenKind::Optional)?;
37                optional = true;
38            }
39            ctx.expect_token(TokenKind::Match)?;
40            let next = self.parse_match_clause(ctx)?;
41            patterns.extend(next.patterns);
42
43            match (where_explicit, next.where_explicit) {
44                (false, true) => where_clause = next.where_clause,
45                (true, true) => {
46                    if let (Some(left), Some(right)) = (&where_clause, &next.where_clause) {
47                        if let Some(combined) = ctx.expression_context().and(left, right) {
48                            where_clause = Some(combined);
49                        }
50                    }
51                }
52                _ => {}
53            }
54            where_explicit |= next.where_explicit;
55
56            if let Some(rc) = next.return_clause {
57                return_clause = Some(rc);
58            }
59            if let Some(dc) = next.delete_clause {
60                delete_clause = Some(dc);
61            }
62            if next.join_hint.is_some() {
63                join_hint = next.join_hint;
64            }
65        }
66
67        let (order_by, limit, skip) = if let Some(ref rc) = return_clause {
68            (rc.order_by.clone(), rc.limit.clone(), rc.skip.clone())
69        } else {
70            (None, None, None)
71        };
72
73        let span = ctx.span_since(start_span.start);
74
75        Ok(Stmt::Match(MatchStmt {
76            span,
77            patterns,
78            join_hint,
79            where_clause,
80            return_clause,
81            order_by,
82            limit,
83            skip,
84            optional,
85            delete_clause,
86        }))
87    }
88
89    pub(crate) fn parse_match_clause(
90        &mut self,
91        ctx: &mut ParseContext,
92    ) -> Result<ParsedMatchClause, ParseError> {
93        let mut patterns = Vec::new();
94        loop {
95            let Some(pattern) = ctx.recover_clause(
96                |c| {
97                    Ok(Some(Pattern::Variable(VariablePattern {
98                        span: c.current_span(),
99                        name: String::new(),
100                    })))
101                },
102                |c| self.parse_pattern(c).map(Some),
103            )?
104            else {
105                break;
106            };
107            patterns.push(pattern);
108            if !ctx.match_token(TokenKind::Comma) {
109                break;
110            }
111        }
112
113        let join_hint = if ctx.check_keyword_sequence(&["USING", "JOIN"]) {
114            Some(self.parse_join_hint(ctx)?)
115        } else {
116            None
117        };
118
119        let (mut where_clause, mut where_explicit) = if ctx.match_token(TokenKind::Where) {
120            (
121                Some(
122                    ctx.recover_clause(Self::create_true_expression, |c| self.parse_expression(c))?,
123                ),
124                true,
125            )
126        } else {
127            (Some(Self::create_true_expression(ctx)?), false)
128        };
129
130        let return_clause = if ctx.match_token(TokenKind::Return) {
131            ctx.recover_clause(
132                |_| Ok(None),
133                |c| ClauseParser::new().parse_return_clause(c).map(Some),
134            )?
135        } else {
136            None
137        };
138
139        let delete_clause = if ctx.match_token(TokenKind::Delete) {
140            ctx.recover_clause(
141                |_| Ok(None),
142                |c| self.parse_match_delete_clause(c).map(Some),
143            )?
144        } else {
145            None
146        };
147
148        if ctx.match_token(TokenKind::Where) {
149            let post =
150                ctx.recover_clause(Self::create_true_expression, |c| self.parse_expression(c))?;
151            if let Some(ref pre) = where_clause {
152                if let Some(combined) = ctx.expression_context().and(pre, &post) {
153                    where_clause = Some(combined);
154                } else {
155                    where_clause = Some(post);
156                }
157            } else {
158                where_clause = Some(post);
159            }
160            where_explicit = true;
161        }
162
163        Ok(ParsedMatchClause {
164            patterns,
165            join_hint,
166            where_clause,
167            where_explicit,
168            return_clause,
169            delete_clause,
170        })
171    }
172
173    fn parse_join_hint(&mut self, ctx: &mut ParseContext) -> Result<JoinHintAst, ParseError> {
174        ctx.consume_keyword("USING")?;
175        ctx.consume_keyword("JOIN")?;
176        if ctx.check_keyword("BINARY") {
177            ctx.consume_keyword("BINARY")?;
178            ctx.expect_token(TokenKind::LParen)?;
179            let left = ctx.expect_identifier()?;
180            ctx.expect_token(TokenKind::Comma)?;
181            let right = ctx.expect_identifier()?;
182            ctx.expect_token(TokenKind::RParen)?;
183            Ok(JoinHintAst::Binary { left, right })
184        } else if ctx.check_keyword("MULTIWAY") {
185            ctx.consume_keyword("MULTIWAY")?;
186            ctx.expect_token(TokenKind::LParen)?;
187            let probe = ctx.expect_identifier()?;
188            let mut builds = Vec::new();
189            while ctx.match_token(TokenKind::Comma) {
190                builds.push(ctx.expect_identifier()?);
191            }
192            ctx.expect_token(TokenKind::RParen)?;
193            if builds.is_empty() {
194                let pos = ctx.current_position();
195                return Err(ParseError::new(
196                    ParseErrorKind::UnexpectedToken,
197                    "MULTIWAY needs at least a probe and one build variable".to_string(),
198                    pos,
199                ));
200            }
201            Ok(JoinHintAst::Multiway { probe, builds })
202        } else {
203            let pos = ctx.current_position();
204            Err(ParseError::new(
205                ParseErrorKind::UnexpectedToken,
206                "Expected BINARY or MULTIWAY after USING JOIN".to_string(),
207                pos,
208            )
209            .with_expected_tokens(vec!["BINARY".to_string(), "MULTIWAY".to_string()]))
210        }
211    }
212
213    fn parse_match_delete_clause(
214        &mut self,
215        ctx: &mut ParseContext,
216    ) -> Result<MatchDeleteClause, ParseError> {
217        ctx.with_edge_syntax_mode(|ctx| {
218            let start_span = ctx.current_span();
219
220            let target = if ctx.match_token(TokenKind::Vertex) {
221                let vertex_ids = self.parse_expression_list(ctx)?;
222                MatchDeleteTarget::Vertices(vertex_ids)
223            } else if ctx.match_token(TokenKind::Edge) {
224                let first_expr = self.parse_expression(ctx)?;
225                if ctx.check_token(TokenKind::Arrow) {
226                    let mut edge_refs = Vec::new();
227                    let mut current_src = first_expr;
228                    loop {
229                        ctx.expect_token(TokenKind::Arrow)?;
230                        let dst = self.parse_expression(ctx)?;
231                        let rank = if ctx.match_token(TokenKind::At) {
232                            Some(self.parse_expression(ctx)?)
233                        } else {
234                            None
235                        };
236                        edge_refs.push((current_src, dst, rank));
237                        if ctx.match_token(TokenKind::Comma) {
238                            current_src = self.parse_expression(ctx)?;
239                        } else {
240                            break;
241                        }
242                    }
243                    MatchDeleteTarget::EdgeRefs(edge_refs)
244                } else {
245                    let mut edge_refs = vec![first_expr];
246                    while ctx.match_token(TokenKind::Comma) {
247                        edge_refs.push(self.parse_expression(ctx)?);
248                    }
249                    MatchDeleteTarget::Edges(edge_refs)
250                }
251            } else {
252                return Err(ParseError::new(
253                    ParseErrorKind::UnexpectedToken,
254                    "Expected VERTEX or EDGE after DELETE".to_string(),
255                    ctx.current_position(),
256                ));
257            };
258
259            let with_edge = if ctx.match_token(TokenKind::With) {
260                ctx.expect_token(TokenKind::Edge)?;
261                true
262            } else {
263                false
264            };
265
266            let span = ctx.span_since(start_span.start);
267
268            Ok(MatchDeleteClause {
269                span,
270                target,
271                with_edge,
272            })
273        })
274    }
275}