Skip to main content

linkrs_query/parser/parsing/dml_parser/
update.rs

1use super::*;
2
3impl DmlParser {
4    /// Analyzing the UPDATE statement (in its complete form, including the UPDATE token)
5    pub fn parse_update_statement(&mut self, ctx: &mut ParseContext) -> Result<Stmt, ParseError> {
6        let start_span = ctx.current_span();
7        ctx.expect_token(TokenKind::Update)?;
8        self.parse_update_after_token(ctx, start_span)
9    }
10
11    /// Analyzing the UPSERT statement (in its complete form, including the UPSERT token)
12    pub fn parse_upsert_statement(&mut self, ctx: &mut ParseContext) -> Result<Stmt, ParseError> {
13        let start_span = ctx.current_span();
14        ctx.expect_token(TokenKind::Upsert)?;
15        ctx.set_upsert_mode(true);
16        let result = self.parse_update_after_token(ctx, start_span);
17        ctx.set_upsert_mode(false);
18        result
19    }
20
21    /// Parse the UPDATE statement after the UPDATE token has been consumed.
22    pub fn parse_update_after_token(
23        &mut self,
24        ctx: &mut ParseContext,
25        start_span: crate::parser::ast::types::Span,
26    ) -> Result<Stmt, ParseError> {
27        use crate::parser::ast::stmt::{SetClause, UpdateStmt, UpdateTarget};
28        use crate::parser::parsing::clause_parser::ClauseParser;
29
30        // Check whether it is UPSERT syntax.
31        let is_upsert = ctx.is_upsert_mode();
32
33        let target = if ctx.match_token(TokenKind::Vertex) {
34            if is_upsert && ctx.check_token(TokenKind::On) {
35                // UPSERT VERTEX ON <tag> SET ... WHERE id(vid) == <n>
36                ctx.match_token(TokenKind::On);
37                let tag_name = ctx.expect_identifier()?;
38                // Parse SET clause
39                let set_clause = if ctx.match_token(TokenKind::Set) {
40                    ClauseParser::new().parse_set_clause(ctx)?
41                } else {
42                    SetClause {
43                        span: ctx.current_span(),
44                        assignments: Vec::new(),
45                    }
46                };
47                // Parse WHERE clause (used to specify vertex ID)
48                let where_clause = if ctx.match_token(TokenKind::Where) {
49                    ctx.recover_clause(|_| Ok(None), |c| self.parse_expression(c).map(Some))?
50                } else {
51                    None
52                };
53                // Parse YIELD clause
54                let yield_clause = if ctx.match_token(TokenKind::Yield) {
55                    Some(ClauseParser::new().parse_yield_clause(ctx)?)
56                } else {
57                    None
58                };
59                // Parse and discard an optional WHEN condition: upsert
60                // semantics do not gate the insert-or-update decision on it.
61                if ctx.match_token(TokenKind::When) {
62                    ctx.recover_clause(|_| Ok(None), |c| self.parse_expression(c).map(Some))?;
63                }
64                // Extract vid from WHERE clause condition. Only the
65                // `id(vid) == <literal>` conjunct is consumed; any remaining
66                // terms stay as a regular filter predicate.
67                let (vid_value_opt, residual) =
68                    Self::extract_id_with_residual(ctx, where_clause.as_ref(), &["vid"]);
69                let vid_value = vid_value_opt.ok_or_else(|| {
70                    ParseError::new(
71                        crate::parser::core::error::ParseErrorKind::SyntaxError,
72                        "UPSERT VERTEX ON form requires WHERE id(vid) == <literal>".to_string(),
73                        ctx.current_position(),
74                    )
75                })?;
76                let vid_expr = CoreExpression::Literal(vid_value);
77                let vid_expr_meta = linkrs_core::types::expr::ExpressionMeta::with_span(
78                    vid_expr,
79                    ctx.current_span(),
80                );
81                let vid_id = ctx
82                    .expression_context_clone()
83                    .register_expression(vid_expr_meta);
84                let vid = ContextualExpression::new(vid_id, ctx.expression_context_clone());
85                let span = ctx.span_since(start_span.start);
86                return Ok(Stmt::Update(UpdateStmt {
87                    span,
88                    target: UpdateTarget::TagOnVertex {
89                        vid: Box::new(vid),
90                        tag_name,
91                    },
92                    set_clause,
93                    where_clause: residual,
94                    yield_clause,
95                    is_upsert,
96                }));
97            }
98            match self.parse_update_vertex(ctx)? {
99                UpdateTarget::Vertex(vid) if ctx.match_token(TokenKind::On) => {
100                    let tag_name = ctx.expect_identifier()?;
101                    UpdateTarget::TagOnVertex {
102                        vid: Box::new(vid),
103                        tag_name,
104                    }
105                }
106                other => other,
107            }
108        } else if ctx.match_token(TokenKind::Tag) {
109            // UPDATE TAG <tag> SET ... WHERE ... (bulk update scoped to one tag).
110            let tag_name = ctx.expect_identifier()?;
111            UpdateTarget::Tag(tag_name)
112        } else if ctx.match_token(TokenKind::Edge) {
113            if is_upsert && ctx.check_token(TokenKind::On) {
114                // UPSERT EDGE ON <edge_type> SET ... WHERE id(src) == <n> AND id(dst) == <m>
115                ctx.match_token(TokenKind::On);
116                let edge_type = ctx.expect_identifier()?;
117                // Parse SET clause
118                let set_clause = if ctx.match_token(TokenKind::Set) {
119                    ClauseParser::new().parse_set_clause(ctx)?
120                } else {
121                    SetClause {
122                        span: ctx.current_span(),
123                        assignments: Vec::new(),
124                    }
125                };
126                // Parse WHERE clause
127                let where_clause = if ctx.match_token(TokenKind::Where) {
128                    ctx.recover_clause(|_| Ok(None), |c| self.parse_expression(c).map(Some))?
129                } else {
130                    None
131                };
132                // Optional edge rank (@rank) trailing the WHERE clause.
133                let rank = if ctx.match_token(TokenKind::At) {
134                    Some(self.parse_expression(ctx)?)
135                } else {
136                    None
137                };
138                // Parse YIELD clause
139                let yield_clause = if ctx.match_token(TokenKind::Yield) {
140                    Some(ClauseParser::new().parse_yield_clause(ctx)?)
141                } else {
142                    None
143                };
144                // Parse and discard an optional WHEN condition: upsert
145                // semantics do not gate the insert-or-update decision on it.
146                if ctx.match_token(TokenKind::When) {
147                    ctx.recover_clause(|_| Ok(None), |c| self.parse_expression(c).map(Some))?;
148                }
149                // Extract src and dst from WHERE clause. Only the
150                // `id(src)/id(dst) == <literal>` conjuncts are consumed; any
151                // remaining terms stay as a regular filter predicate.
152                let (src_value_opt, after_src) =
153                    Self::extract_id_with_residual(ctx, where_clause.as_ref(), &["src", "source"]);
154                let src_value = src_value_opt.ok_or_else(|| {
155                    ParseError::new(
156                        crate::parser::core::error::ParseErrorKind::SyntaxError,
157                        "UPSERT EDGE ON form requires WHERE id(src) == <literal> AND id(dst) == <literal>"
158                            .to_string(),
159                        ctx.current_position(),
160                    )
161                })?;
162                let (dst_value_opt, residual) = Self::extract_id_with_residual(
163                    ctx,
164                    after_src.as_ref(),
165                    &["dst", "dest", "destination"],
166                );
167                let dst_value = dst_value_opt.ok_or_else(|| {
168                    ParseError::new(
169                        crate::parser::core::error::ParseErrorKind::SyntaxError,
170                        "UPSERT EDGE ON form requires WHERE id(src) == <literal> AND id(dst) == <literal>"
171                            .to_string(),
172                        ctx.current_position(),
173                    )
174                })?;
175                let make_literal_expr =
176                    |ctx: &mut ParseContext, value: linkrs_core::Value| -> ContextualExpression {
177                        let expr = CoreExpression::Literal(value);
178                        let meta = linkrs_core::types::expr::ExpressionMeta::with_span(
179                            expr,
180                            ctx.current_span(),
181                        );
182                        let id = ctx.expression_context_clone().register_expression(meta);
183                        ContextualExpression::new(id, ctx.expression_context_clone())
184                    };
185                let src = make_literal_expr(ctx, src_value);
186                let dst = make_literal_expr(ctx, dst_value);
187                let span = ctx.span_since(start_span.start);
188                return Ok(Stmt::Update(UpdateStmt {
189                    span,
190                    target: UpdateTarget::Edge {
191                        edge_type: Some(edge_type),
192                        src,
193                        dst,
194                        rank,
195                    },
196                    set_clause,
197                    where_clause: residual,
198                    is_upsert,
199                    yield_clause,
200                }));
201            }
202            self.parse_update_edge(ctx)?
203        } else {
204            // Check whether the syntax is correct for the UPSERT VERTEX vid ON tag_name command.
205            if is_upsert {
206                let vid = self.parse_expression(ctx)?;
207                if ctx.match_token(TokenKind::On) {
208                    let tag_name = ctx.expect_identifier()?;
209                    UpdateTarget::TagOnVertex {
210                        vid: Box::new(vid),
211                        tag_name,
212                    }
213                } else {
214                    UpdateTarget::Vertex(vid)
215                }
216            } else {
217                // A reserved keyword cannot start the update target.  Consume
218                // the keyword so error recovery cannot reinterpret the rest
219                // of the input as a different statement.
220                if matches!(
221                    ctx.current_token().kind,
222                    TokenKind::Set
223                        | TokenKind::Where
224                        | TokenKind::When
225                        | TokenKind::Yield
226                        | TokenKind::On
227                        | TokenKind::Of
228                ) {
229                    ctx.next_token();
230                    return Err(ParseError::new(
231                        crate::parser::core::error::ParseErrorKind::UnexpectedToken,
232                        "Expected vertex id or edge target after UPDATE".to_string(),
233                        ctx.current_position(),
234                    ));
235                }
236                // Check for old-style edge update syntax: src -> dst OF edge_type
237                // Try to parse as expression first, then check if followed by ->
238                let expr = self.parse_expression(ctx)?;
239                if ctx.check_token(TokenKind::Arrow) {
240                    // This is edge update syntax: src -> dst [@rank] OF edge_type
241                    self.parse_update_edge_short(ctx, expr)?
242                } else if ctx.match_token(TokenKind::On) {
243                    let tag_name = ctx.expect_identifier()?;
244                    UpdateTarget::TagOnVertex {
245                        vid: Box::new(expr),
246                        tag_name,
247                    }
248                } else {
249                    // Regular vertex update
250                    UpdateTarget::Vertex(expr)
251                }
252            }
253        };
254
255        let set_clause = if ctx.match_token(TokenKind::Set) {
256            ClauseParser::new().parse_set_clause(ctx)?
257        } else {
258            SetClause {
259                span: ctx.current_span(),
260                assignments: Vec::new(),
261            }
262        };
263
264        let where_clause = if ctx.match_token(TokenKind::Where) || ctx.match_token(TokenKind::When)
265        {
266            ctx.recover_clause(|_| Ok(None), |c| self.parse_expression(c).map(Some))?
267        } else {
268            None
269        };
270
271        // Parse YIELD clause
272        let yield_clause = if ctx.match_token(TokenKind::Yield) {
273            Some(ClauseParser::new().parse_yield_clause(ctx)?)
274        } else {
275            None
276        };
277
278        let span = ctx.span_since(start_span.start);
279
280        Ok(Stmt::Update(UpdateStmt {
281            span,
282            target,
283            set_clause,
284            where_clause,
285            is_upsert,
286            yield_clause,
287        }))
288    }
289
290    fn parse_update_vertex(&mut self, ctx: &mut ParseContext) -> Result<UpdateTarget, ParseError> {
291        let vid = self.parse_expression(ctx)?;
292        Ok(UpdateTarget::Vertex(vid))
293    }
294
295    fn parse_update_edge(&mut self, ctx: &mut ParseContext) -> Result<UpdateTarget, ParseError> {
296        // `->` is an edge arrow inside this statement, not the JSON access
297        // operator, so expression parsing must not treat `-> 'key'` as a
298        // postfix JSON get.
299        ctx.with_edge_syntax_mode(|ctx| {
300            // Check whether it is UPSERT EDGE syntax: src -> dst @rank OF edge_type
301            // or UPDATE EDGE Syntax: OF edge_type FROM src TO dst [@rank]
302            // or UPDATE EDGE Syntax (short): src -> dst [@rank] OF edge_type
303            let is_upsert = ctx.is_upsert_mode();
304            // UPSERT EDGE always uses the short shape.
305            // For UPDATE, distinguish by syntax, not by whether the endpoint is a literal.
306            if is_upsert || ctx.check_token(TokenKind::Of) == false {
307                // UPDATE/UPSERT EDGE short shape: src -> dst [@rank] OF edge_type.
308                // We already hold `src` if the caller parsed nothing; parse it here.
309                let src = if ctx.check_token(TokenKind::Arrow) {
310                    // Defensive: caller unexpectedly consumed nothing; handled below.
311                    return Err(ParseError::new(
312                        ParseErrorKind::UnexpectedToken,
313                        "Expected edge source expression before ->".to_string(),
314                        ctx.current_position(),
315                    ));
316                } else {
317                    self.parse_expression(ctx)?
318                };
319                ctx.expect_token(TokenKind::Arrow)?;
320                let dst = self.parse_expression(ctx)?;
321
322                let rank = if ctx.match_token(TokenKind::At) {
323                    Some(self.parse_expression(ctx)?)
324                } else {
325                    None
326                };
327
328                ctx.expect_token(TokenKind::Of)?;
329                let edge_type = Some(ctx.expect_identifier()?);
330
331                Ok(UpdateTarget::Edge {
332                    edge_type,
333                    src,
334                    dst,
335                    rank,
336                })
337            } else {
338                // UPDATE EDGE long shape: OF edge_type FROM src TO dst [@rank].
339                ctx.expect_token(TokenKind::Of)?;
340
341                // Analyzing edge types
342                let edge_type = ctx.expect_identifier()?;
343
344                // Analyzing src and dst
345                ctx.expect_token(TokenKind::From)?;
346                let src = self.parse_expression(ctx)?;
347
348                ctx.expect_token(TokenKind::To)?;
349                let dst = self.parse_expression(ctx)?;
350
351                // Analysis of @rank (optional)
352                let rank = if ctx.match_token(TokenKind::At) {
353                    Some(self.parse_expression(ctx)?)
354                } else {
355                    None
356                };
357
358                Ok(UpdateTarget::Edge {
359                    edge_type: Some(edge_type),
360                    src,
361                    dst,
362                    rank,
363                })
364            }
365        })
366    }
367
368    /// Parse short edge update syntax: src -> dst [@rank] OF edge_type
369    /// This is called after parsing the src expression and seeing the -> token
370    fn parse_update_edge_short(
371        &mut self,
372        ctx: &mut ParseContext,
373        src: ContextualExpression,
374    ) -> Result<UpdateTarget, ParseError> {
375        use crate::parser::ast::stmt::UpdateTarget;
376
377        ctx.with_edge_syntax_mode(|ctx| {
378            // The -> token
379            ctx.expect_token(TokenKind::Arrow)?;
380
381            // Parse dst
382            let dst = self.parse_expression(ctx)?;
383
384            // Optional @rank
385            let rank = if ctx.match_token(TokenKind::At) {
386                Some(self.parse_expression(ctx)?)
387            } else {
388                None
389            };
390
391            // OF edge_type
392            ctx.expect_token(TokenKind::Of)?;
393            let edge_type = Some(ctx.expect_identifier()?);
394
395            Ok(UpdateTarget::Edge {
396                edge_type,
397                src,
398                dst,
399                rank,
400            })
401        })
402    }
403}