Skip to main content

cypherlite_query/parser/
clause.rs

1// Clause parsing: MATCH, RETURN, CREATE, SET, DELETE, REMOVE, WITH, MERGE
2
3use super::ast::*;
4use super::{ParseError, Parser};
5use crate::lexer::Token;
6
7impl<'a> Parser<'a> {
8    /// Parse a MATCH clause (TASK-028).
9    ///
10    /// Expects the parser to be positioned at the MATCH keyword.
11    /// The `optional` flag indicates whether OPTIONAL was already consumed
12    /// by the caller.
13    ///
14    /// Grammar: MATCH pattern [WHERE expression]
15    pub fn parse_match_clause(&mut self, optional: bool) -> Result<MatchClause, ParseError> {
16        self.expect(&Token::Match)?;
17        let pattern = self.parse_pattern()?;
18
19        // Parse optional temporal predicate: AT TIME <expr> | BETWEEN TIME <expr> AND <expr>
20        let temporal_predicate = self.parse_optional_temporal_predicate()?;
21
22        let where_clause = if self.eat(&Token::Where) {
23            Some(self.parse_expression()?)
24        } else {
25            None
26        };
27
28        Ok(MatchClause {
29            optional,
30            pattern,
31            temporal_predicate,
32            where_clause,
33        })
34    }
35
36    /// Parse a RETURN clause (TASK-029, TASK-030).
37    ///
38    /// Grammar: `RETURN [DISTINCT] items [ORDER BY order_items] [SKIP expr] [LIMIT expr]`
39    pub fn parse_return_clause(&mut self) -> Result<ReturnClause, ParseError> {
40        self.expect(&Token::Return)?;
41
42        let distinct = self.eat(&Token::Distinct);
43
44        let items = self.parse_return_items()?;
45
46        let order_by = if self.check(&Token::Order) {
47            Some(self.parse_order_by()?)
48        } else {
49            None
50        };
51
52        let skip = if self.eat(&Token::Skip) {
53            Some(self.parse_expression()?)
54        } else {
55            None
56        };
57
58        let limit = if self.eat(&Token::Limit) {
59            Some(self.parse_expression()?)
60        } else {
61            None
62        };
63
64        Ok(ReturnClause {
65            distinct,
66            items,
67            order_by,
68            skip,
69            limit,
70        })
71    }
72
73    /// Parse a CREATE clause (TASK-031).
74    ///
75    /// Grammar: CREATE pattern
76    pub fn parse_create_clause(&mut self) -> Result<CreateClause, ParseError> {
77        self.expect(&Token::Create)?;
78        let pattern = self.parse_pattern()?;
79        Ok(CreateClause { pattern })
80    }
81
82    /// Parse a SET clause (TASK-032).
83    ///
84    /// Grammar: SET property = expression [, property = expression]*
85    pub fn parse_set_clause(&mut self) -> Result<SetClause, ParseError> {
86        self.expect(&Token::Set)?;
87
88        let mut items = Vec::new();
89        items.push(self.parse_set_item()?);
90        while self.eat(&Token::Comma) {
91            items.push(self.parse_set_item()?);
92        }
93
94        Ok(SetClause { items })
95    }
96
97    /// Parse a REMOVE clause (TASK-032).
98    ///
99    /// Grammar: REMOVE (property_access | variable:Label) [, ...]*
100    pub fn parse_remove_clause(&mut self) -> Result<RemoveClause, ParseError> {
101        self.expect(&Token::Remove)?;
102
103        let mut items = Vec::new();
104        items.push(self.parse_remove_item()?);
105        while self.eat(&Token::Comma) {
106            items.push(self.parse_remove_item()?);
107        }
108
109        Ok(RemoveClause { items })
110    }
111
112    /// Parse a DELETE clause (TASK-033).
113    ///
114    /// Grammar: DELETE expression [, expression]*
115    /// The `detach` flag indicates whether DETACH was already consumed.
116    pub fn parse_delete_clause(&mut self, detach: bool) -> Result<DeleteClause, ParseError> {
117        self.expect(&Token::Delete)?;
118
119        let mut exprs = Vec::new();
120        exprs.push(self.parse_expression()?);
121        while self.eat(&Token::Comma) {
122            exprs.push(self.parse_expression()?);
123        }
124
125        Ok(DeleteClause { detach, exprs })
126    }
127
128    /// Parse a WITH clause (P2 syntax -- parsed but execution returns UnsupportedSyntax).
129    ///
130    /// Grammar: `WITH [DISTINCT] items [WHERE expression]`
131    pub fn parse_with_clause(&mut self) -> Result<WithClause, ParseError> {
132        self.expect(&Token::With)?;
133
134        let distinct = self.eat(&Token::Distinct);
135        let items = self.parse_return_items()?;
136
137        let where_clause = if self.eat(&Token::Where) {
138            Some(self.parse_expression()?)
139        } else {
140            None
141        };
142
143        Ok(WithClause {
144            distinct,
145            items,
146            where_clause,
147        })
148    }
149
150    /// Parse an UNWIND clause (TASK-068).
151    ///
152    /// Grammar: UNWIND expression AS variable
153    pub fn parse_unwind_clause(&mut self) -> Result<UnwindClause, ParseError> {
154        self.expect(&Token::Unwind)?;
155        let expr = self.parse_expression()?;
156        self.expect(&Token::As)?;
157        let variable = self.expect_ident()?;
158        Ok(UnwindClause { expr, variable })
159    }
160
161    /// Parse a MERGE clause.
162    ///
163    /// Grammar: MERGE pattern [ON MATCH SET items] [ON CREATE SET items]
164    pub fn parse_merge_clause(&mut self) -> Result<MergeClause, ParseError> {
165        self.expect(&Token::Merge)?;
166        let pattern = self.parse_pattern()?;
167
168        let mut on_match = Vec::new();
169        let mut on_create = Vec::new();
170
171        // Parse optional ON MATCH SET and ON CREATE SET (can appear in any order)
172        loop {
173            if self.check(&Token::On) {
174                // Peek at the token after ON
175                let next = self.tokens.get(self.pos + 1).map(|(t, _)| t.clone());
176                match next {
177                    Some(Token::Match) => {
178                        self.advance(); // consume ON
179                        self.advance(); // consume MATCH
180                        self.expect(&Token::Set)?;
181                        on_match.push(self.parse_set_item()?);
182                        while self.eat(&Token::Comma) {
183                            on_match.push(self.parse_set_item()?);
184                        }
185                    }
186                    Some(Token::Create) => {
187                        self.advance(); // consume ON
188                        self.advance(); // consume CREATE
189                        self.expect(&Token::Set)?;
190                        on_create.push(self.parse_set_item()?);
191                        while self.eat(&Token::Comma) {
192                            on_create.push(self.parse_set_item()?);
193                        }
194                    }
195                    _ => break,
196                }
197            } else {
198                break;
199            }
200        }
201
202        Ok(MergeClause {
203            pattern,
204            on_match,
205            on_create,
206        })
207    }
208
209    /// Parse a CREATE INDEX clause (TASK-098).
210    ///
211    /// Grammar: `CREATE INDEX [name] ON :Label(property)`
212    /// The parser has already consumed CREATE and is positioned at INDEX.
213    pub fn parse_create_index_clause(&mut self) -> Result<CreateIndexClause, ParseError> {
214        self.expect(&Token::Index)?;
215
216        // Optional index name (identifier before ON)
217        let name = if !self.check(&Token::On) {
218            Some(self.expect_ident()?)
219        } else {
220            None
221        };
222
223        self.expect(&Token::On)?;
224        self.expect(&Token::Colon)?;
225        let label = self.expect_ident()?;
226        self.expect(&Token::LParen)?;
227        let property = self.expect_ident()?;
228        self.expect(&Token::RParen)?;
229
230        Ok(CreateIndexClause {
231            name,
232            target: IndexTarget::NodeLabel(label),
233            property,
234        })
235    }
236
237    /// Parse a CREATE EDGE INDEX clause (CC-T3).
238    ///
239    /// Grammar: `CREATE EDGE INDEX [name] ON :RelType(property)`
240    /// The parser has already consumed CREATE and is positioned at EDGE.
241    pub fn parse_create_edge_index_clause(&mut self) -> Result<CreateIndexClause, ParseError> {
242        self.expect(&Token::Edge)?;
243        self.expect(&Token::Index)?;
244
245        // Optional index name (identifier before ON)
246        let name = if !self.check(&Token::On) {
247            Some(self.expect_ident()?)
248        } else {
249            None
250        };
251
252        self.expect(&Token::On)?;
253        self.expect(&Token::Colon)?;
254        let rel_type = self.expect_ident()?;
255        self.expect(&Token::LParen)?;
256        let property = self.expect_ident()?;
257        self.expect(&Token::RParen)?;
258
259        Ok(CreateIndexClause {
260            name,
261            target: IndexTarget::RelationshipType(rel_type),
262            property,
263        })
264    }
265
266    /// Parse a DROP INDEX clause (TASK-098).
267    ///
268    /// Grammar: DROP INDEX name
269    pub fn parse_drop_index_clause(&mut self) -> Result<DropIndexClause, ParseError> {
270        self.expect(&Token::Drop)?;
271        self.expect(&Token::Index)?;
272        let name = self.expect_ident()?;
273        Ok(DropIndexClause { name })
274    }
275
276    /// Parse a CREATE SNAPSHOT clause (HH-001).
277    ///
278    /// Grammar: CREATE SNAPSHOT (var:Label {props}) [AT TIME expr] FROM MATCH pattern [WHERE filter] RETURN items
279    /// The parser has already consumed CREATE and is positioned at SNAPSHOT.
280    #[cfg(feature = "subgraph")]
281    pub fn parse_create_snapshot_clause(&mut self) -> Result<CreateSnapshotClause, ParseError> {
282        self.expect(&Token::Snapshot)?;
283
284        // Parse node pattern: (var:Label {props})
285        self.expect(&Token::LParen)?;
286
287        // Optional variable
288        let variable = if self.check(&Token::Colon) {
289            None
290        } else {
291            match self.peek() {
292                Some(Token::Ident(_) | Token::BacktickIdent(_)) => Some(self.expect_ident()?),
293                _ => None,
294            }
295        };
296
297        // Labels
298        let mut labels = Vec::new();
299        while self.eat(&Token::Colon) {
300            labels.push(self.expect_ident()?);
301        }
302
303        // Optional properties
304        let properties = if self.check(&Token::LBrace) {
305            Some(self.parse_map_literal()?)
306        } else {
307            None
308        };
309
310        self.expect(&Token::RParen)?;
311
312        // Optional AT TIME expr
313        let temporal_anchor = if self.check(&Token::At) {
314            self.advance(); // consume AT
315            self.expect(&Token::Time)?;
316            Some(self.parse_expression()?)
317        } else {
318            None
319        };
320
321        // FROM MATCH ... [WHERE ...] RETURN ...
322        self.expect(&Token::From)?;
323        let from_match = self.parse_match_clause(false)?;
324
325        self.expect(&Token::Return)?;
326        let from_return = self.parse_return_items()?;
327
328        Ok(CreateSnapshotClause {
329            variable,
330            labels,
331            properties,
332            temporal_anchor,
333            from_match,
334            from_return,
335        })
336    }
337
338    /// Parse a CREATE HYPEREDGE clause (MM-001).
339    ///
340    /// Grammar: CREATE HYPEREDGE (var:Label) FROM (expr [AT TIME expr], ...) TO (expr [AT TIME expr], ...)
341    /// The parser has already consumed CREATE and is positioned at HYPEREDGE.
342    #[cfg(feature = "hypergraph")]
343    pub fn parse_create_hyperedge_clause(&mut self) -> Result<CreateHyperedgeClause, ParseError> {
344        self.expect(&Token::Hyperedge)?;
345
346        // Parse (var:Label) pattern
347        self.expect(&Token::LParen)?;
348
349        // Optional variable
350        let variable = if self.check(&Token::Colon) {
351            None
352        } else {
353            match self.peek() {
354                Some(Token::Ident(_) | Token::BacktickIdent(_)) => Some(self.expect_ident()?),
355                _ => None,
356            }
357        };
358
359        // Labels
360        let mut labels = Vec::new();
361        while self.eat(&Token::Colon) {
362            labels.push(self.expect_ident()?);
363        }
364
365        self.expect(&Token::RParen)?;
366
367        // FROM (participant_list)
368        self.expect(&Token::From)?;
369        let sources = self.parse_hyperedge_participant_list()?;
370
371        // TO (participant_list) -- TO is parsed as an Ident since it's not a keyword
372        self.expect_to_keyword()?;
373        let targets = self.parse_hyperedge_participant_list()?;
374
375        Ok(CreateHyperedgeClause {
376            variable,
377            labels,
378            sources,
379            targets,
380        })
381    }
382
383    /// Parse a MATCH HYPEREDGE clause (MM-003).
384    ///
385    /// Grammar: MATCH HYPEREDGE (var:Label)
386    /// The parser has already consumed MATCH and is positioned at HYPEREDGE.
387    #[cfg(feature = "hypergraph")]
388    pub fn parse_match_hyperedge_clause(&mut self) -> Result<MatchHyperedgeClause, ParseError> {
389        self.expect(&Token::Hyperedge)?;
390
391        self.expect(&Token::LParen)?;
392
393        // Optional variable
394        let variable = if self.check(&Token::Colon) {
395            None
396        } else {
397            match self.peek() {
398                Some(Token::Ident(_) | Token::BacktickIdent(_)) => {
399                    // Check if next token is : or ) to determine if this is a variable
400                    Some(self.expect_ident()?)
401                }
402                _ => None,
403            }
404        };
405
406        // Labels
407        let mut labels = Vec::new();
408        while self.eat(&Token::Colon) {
409            labels.push(self.expect_ident()?);
410        }
411
412        self.expect(&Token::RParen)?;
413
414        Ok(MatchHyperedgeClause { variable, labels })
415    }
416
417    /// Parse a parenthesized comma-separated list of hyperedge participants.
418    /// Each participant is an expression, optionally followed by AT TIME expr.
419    #[cfg(feature = "hypergraph")]
420    fn parse_hyperedge_participant_list(&mut self) -> Result<Vec<Expression>, ParseError> {
421        self.expect(&Token::LParen)?;
422        let mut participants = Vec::new();
423
424        if !self.check(&Token::RParen) {
425            participants.push(self.parse_hyperedge_participant()?);
426            while self.eat(&Token::Comma) {
427                participants.push(self.parse_hyperedge_participant()?);
428            }
429        }
430
431        self.expect(&Token::RParen)?;
432        Ok(participants)
433    }
434
435    /// Parse a single hyperedge participant: expression [AT TIME expression].
436    #[cfg(feature = "hypergraph")]
437    fn parse_hyperedge_participant(&mut self) -> Result<Expression, ParseError> {
438        // Parse the base expression (usually a variable reference).
439        // We use expect_ident() for simplicity since participants are variable refs.
440        let name = self.expect_ident()?;
441        let base_expr = Expression::Variable(name);
442
443        // Check for optional AT TIME temporal modifier.
444        if self.check(&Token::At) {
445            self.advance(); // consume AT
446            self.expect(&Token::Time)?;
447            let timestamp = self.parse_expression()?;
448            Ok(Expression::TemporalRef {
449                node: Box::new(base_expr),
450                timestamp: Box::new(timestamp),
451            })
452        } else {
453            Ok(base_expr)
454        }
455    }
456
457    /// Expect a "TO" keyword, which is parsed as an identifier since it's not a reserved token.
458    #[cfg(feature = "hypergraph")]
459    fn expect_to_keyword(&mut self) -> Result<(), ParseError> {
460        match self.tokens.get(self.pos) {
461            Some((Token::Ident(name), _)) if name.eq_ignore_ascii_case("TO") => {
462                self.pos += 1;
463                Ok(())
464            }
465            _ => Err(self.error("expected TO keyword")),
466        }
467    }
468
469    /// Parse an optional temporal predicate after MATCH pattern.
470    ///
471    /// Grammar:
472    ///   AT TIME <expression>
473    ///   BETWEEN TIME <expression> AND <expression>
474    fn parse_optional_temporal_predicate(
475        &mut self,
476    ) -> Result<Option<TemporalPredicate>, ParseError> {
477        if self.check(&Token::At) {
478            // AT TIME <expr>
479            self.advance(); // consume AT
480            self.expect(&Token::Time)?;
481            let expr = self.parse_expression()?;
482            Ok(Some(TemporalPredicate::AsOf(expr)))
483        } else if self.check(&Token::Between) {
484            // BETWEEN TIME <expr> AND <expr>
485            self.advance(); // consume BETWEEN
486            self.expect(&Token::Time)?;
487            let start = self.parse_expression_no_and()?;
488            self.expect(&Token::And)?;
489            let end = self.parse_expression_no_and()?;
490            Ok(Some(TemporalPredicate::Between(start, end)))
491        } else {
492            Ok(None)
493        }
494    }
495
496    // -- Helper functions --
497
498    /// Parse comma-separated return items: expression [AS alias] [, ...]
499    fn parse_return_items(&mut self) -> Result<Vec<ReturnItem>, ParseError> {
500        let mut items = Vec::new();
501        items.push(self.parse_return_item()?);
502        while self.eat(&Token::Comma) {
503            items.push(self.parse_return_item()?);
504        }
505        Ok(items)
506    }
507
508    /// Parse a single return item: expression [AS alias]
509    fn parse_return_item(&mut self) -> Result<ReturnItem, ParseError> {
510        let expr = self.parse_expression()?;
511        let alias = if self.eat(&Token::As) {
512            Some(self.expect_ident()?)
513        } else {
514            None
515        };
516        Ok(ReturnItem { expr, alias })
517    }
518
519    /// Parse ORDER BY clause: ORDER BY expression [ASC|DESC] [, ...]
520    fn parse_order_by(&mut self) -> Result<Vec<OrderItem>, ParseError> {
521        self.expect(&Token::Order)?;
522        self.expect(&Token::By)?;
523
524        let mut items = Vec::new();
525        items.push(self.parse_order_item()?);
526        while self.eat(&Token::Comma) {
527            items.push(self.parse_order_item()?);
528        }
529        Ok(items)
530    }
531
532    /// Parse a single order item: expression [ASC|DESC]
533    fn parse_order_item(&mut self) -> Result<OrderItem, ParseError> {
534        let expr = self.parse_expression()?;
535        let ascending = if self.eat(&Token::Desc) {
536            false
537        } else {
538            // ASC is default; consume it if present
539            self.eat(&Token::Asc);
540            true
541        };
542        Ok(OrderItem { expr, ascending })
543    }
544
545    /// Parse a single SET item: variable.property = expression
546    ///
547    /// We cannot use parse_expression() for the target because it would
548    /// consume the `=` as a comparison operator. Instead, we parse the
549    /// property access manually: ident (.ident)*
550    fn parse_set_item(&mut self) -> Result<SetItem, ParseError> {
551        let name = self.expect_ident()?;
552        let mut target = Expression::Variable(name);
553        // Parse property chain: .prop1.prop2...
554        while self.eat(&Token::Dot) {
555            let prop = self.expect_ident()?;
556            target = Expression::Property(Box::new(target), prop);
557        }
558        self.expect(&Token::Eq)?;
559        let value = self.parse_expression()?;
560        Ok(SetItem::Property { target, value })
561    }
562
563    /// Parse a single REMOVE item: property_access or variable:Label
564    fn parse_remove_item(&mut self) -> Result<RemoveItem, ParseError> {
565        let name = self.expect_ident()?;
566
567        if self.eat(&Token::Colon) {
568            // variable:Label
569            let label = self.expect_ident()?;
570            Ok(RemoveItem::Label {
571                variable: name,
572                label,
573            })
574        } else if self.eat(&Token::Dot) {
575            // variable.property -- build the property access expression
576            let prop = self.expect_ident()?;
577            let expr = Expression::Property(Box::new(Expression::Variable(name)), prop);
578            Ok(RemoveItem::Property(expr))
579        } else {
580            Err(self.error("expected '.' or ':' after identifier in REMOVE item"))
581        }
582    }
583}
584
585// ---------------------------------------------------------------------------
586// Tests
587// ---------------------------------------------------------------------------
588
589#[cfg(test)]
590mod tests {
591    use super::*;
592    use crate::lexer::lex;
593
594    /// Helper: create a parser from an input string.
595    fn make_parser(input: &str) -> (Vec<(Token, crate::lexer::Span)>, String) {
596        let tokens = lex(input).expect("lexing should succeed");
597        (tokens, input.to_string())
598    }
599
600    // ======================================================================
601    // TASK-028: parse_match_clause
602    // ======================================================================
603
604    #[test]
605    fn match_simple_node() {
606        let (tokens, input) = make_parser("MATCH (n:Person)");
607        let mut p = Parser::new(&tokens, &input);
608        let mc = p.parse_match_clause(false).expect("should parse");
609
610        assert!(!mc.optional);
611        assert_eq!(mc.pattern.chains.len(), 1);
612        let node = match &mc.pattern.chains[0].elements[0] {
613            PatternElement::Node(n) => n,
614            _ => panic!("expected node"),
615        };
616        assert_eq!(node.variable, Some("n".to_string()));
617        assert_eq!(node.labels, vec!["Person".to_string()]);
618        assert!(mc.where_clause.is_none());
619    }
620
621    #[test]
622    fn match_with_where() {
623        let (tokens, input) = make_parser("MATCH (n:Person) WHERE n.age > 30");
624        let mut p = Parser::new(&tokens, &input);
625        let mc = p.parse_match_clause(false).expect("should parse");
626
627        assert!(mc.where_clause.is_some());
628        let where_expr = mc.where_clause.expect("checked above");
629        assert!(matches!(
630            where_expr,
631            Expression::BinaryOp(BinaryOp::Gt, _, _)
632        ));
633    }
634
635    #[test]
636    fn match_optional() {
637        let (tokens, input) = make_parser("MATCH (n)");
638        let mut p = Parser::new(&tokens, &input);
639        let mc = p.parse_match_clause(true).expect("should parse");
640
641        assert!(mc.optional);
642    }
643
644    #[test]
645    fn match_with_relationship() {
646        let (tokens, input) = make_parser("MATCH (a)-[:KNOWS]->(b)");
647        let mut p = Parser::new(&tokens, &input);
648        let mc = p.parse_match_clause(false).expect("should parse");
649
650        assert_eq!(mc.pattern.chains[0].elements.len(), 3);
651    }
652
653    // ======================================================================
654    // TASK-029 / TASK-030: parse_return_clause (with ORDER BY, SKIP, LIMIT)
655    // ======================================================================
656
657    #[test]
658    fn return_simple_variable() {
659        let (tokens, input) = make_parser("RETURN n");
660        let mut p = Parser::new(&tokens, &input);
661        let rc = p.parse_return_clause().expect("should parse");
662
663        assert!(!rc.distinct);
664        assert_eq!(rc.items.len(), 1);
665        assert_eq!(rc.items[0].expr, Expression::Variable("n".to_string()));
666        assert!(rc.items[0].alias.is_none());
667        assert!(rc.order_by.is_none());
668        assert!(rc.skip.is_none());
669        assert!(rc.limit.is_none());
670    }
671
672    #[test]
673    fn return_distinct() {
674        let (tokens, input) = make_parser("RETURN DISTINCT n.name");
675        let mut p = Parser::new(&tokens, &input);
676        let rc = p.parse_return_clause().expect("should parse");
677
678        assert!(rc.distinct);
679    }
680
681    #[test]
682    fn return_multiple_with_alias() {
683        let (tokens, input) = make_parser("RETURN n.name AS name, n.age AS age");
684        let mut p = Parser::new(&tokens, &input);
685        let rc = p.parse_return_clause().expect("should parse");
686
687        assert_eq!(rc.items.len(), 2);
688        assert_eq!(rc.items[0].alias, Some("name".to_string()));
689        assert_eq!(rc.items[1].alias, Some("age".to_string()));
690    }
691
692    #[test]
693    fn return_with_order_by() {
694        let (tokens, input) = make_parser("RETURN n.name ORDER BY n.name ASC");
695        let mut p = Parser::new(&tokens, &input);
696        let rc = p.parse_return_clause().expect("should parse");
697
698        let order = rc.order_by.expect("should have ORDER BY");
699        assert_eq!(order.len(), 1);
700        assert!(order[0].ascending);
701    }
702
703    #[test]
704    fn return_with_order_by_desc() {
705        let (tokens, input) = make_parser("RETURN n ORDER BY n.age DESC");
706        let mut p = Parser::new(&tokens, &input);
707        let rc = p.parse_return_clause().expect("should parse");
708
709        let order = rc.order_by.expect("should have ORDER BY");
710        assert!(!order[0].ascending);
711    }
712
713    #[test]
714    fn return_with_order_by_multiple() {
715        let (tokens, input) = make_parser("RETURN n ORDER BY n.name ASC, n.age DESC");
716        let mut p = Parser::new(&tokens, &input);
717        let rc = p.parse_return_clause().expect("should parse");
718
719        let order = rc.order_by.expect("should have ORDER BY");
720        assert_eq!(order.len(), 2);
721        assert!(order[0].ascending);
722        assert!(!order[1].ascending);
723    }
724
725    #[test]
726    fn return_with_skip() {
727        let (tokens, input) = make_parser("RETURN n SKIP 5");
728        let mut p = Parser::new(&tokens, &input);
729        let rc = p.parse_return_clause().expect("should parse");
730
731        assert_eq!(
732            rc.skip.expect("should have SKIP"),
733            Expression::Literal(Literal::Integer(5))
734        );
735    }
736
737    #[test]
738    fn return_with_limit() {
739        let (tokens, input) = make_parser("RETURN n LIMIT 10");
740        let mut p = Parser::new(&tokens, &input);
741        let rc = p.parse_return_clause().expect("should parse");
742
743        assert_eq!(
744            rc.limit.expect("should have LIMIT"),
745            Expression::Literal(Literal::Integer(10))
746        );
747    }
748
749    #[test]
750    fn return_with_order_skip_limit() {
751        let (tokens, input) = make_parser("RETURN n ORDER BY n.name SKIP 5 LIMIT 10");
752        let mut p = Parser::new(&tokens, &input);
753        let rc = p.parse_return_clause().expect("should parse");
754
755        assert!(rc.order_by.is_some());
756        assert!(rc.skip.is_some());
757        assert!(rc.limit.is_some());
758    }
759
760    #[test]
761    fn return_order_by_default_ascending() {
762        let (tokens, input) = make_parser("RETURN n ORDER BY n.name");
763        let mut p = Parser::new(&tokens, &input);
764        let rc = p.parse_return_clause().expect("should parse");
765
766        let order = rc.order_by.expect("should have ORDER BY");
767        assert!(order[0].ascending); // default is ASC
768    }
769
770    // ======================================================================
771    // TASK-031: parse_create_clause
772    // ======================================================================
773
774    #[test]
775    fn create_simple_node() {
776        let (tokens, input) = make_parser("CREATE (n:Person)");
777        let mut p = Parser::new(&tokens, &input);
778        let cc = p.parse_create_clause().expect("should parse");
779
780        let node = match &cc.pattern.chains[0].elements[0] {
781            PatternElement::Node(n) => n,
782            _ => panic!("expected node"),
783        };
784        assert_eq!(node.variable, Some("n".to_string()));
785        assert_eq!(node.labels, vec!["Person".to_string()]);
786    }
787
788    #[test]
789    fn create_node_with_properties() {
790        let (tokens, input) = make_parser("CREATE (n:Person {name: 'Alice', age: 30})");
791        let mut p = Parser::new(&tokens, &input);
792        let cc = p.parse_create_clause().expect("should parse");
793
794        let node = match &cc.pattern.chains[0].elements[0] {
795            PatternElement::Node(n) => n,
796            _ => panic!("expected node"),
797        };
798        assert!(node.properties.is_some());
799        let props = node.properties.as_ref().expect("checked above");
800        assert_eq!(props.len(), 2);
801    }
802
803    #[test]
804    fn create_relationship() {
805        let (tokens, input) =
806            make_parser("CREATE (a:Person {name: 'Alice'})-[:KNOWS]->(b:Person {name: 'Bob'})");
807        let mut p = Parser::new(&tokens, &input);
808        let cc = p.parse_create_clause().expect("should parse");
809
810        assert_eq!(cc.pattern.chains[0].elements.len(), 3);
811    }
812
813    // ======================================================================
814    // TASK-032: parse_set_clause / parse_remove_clause
815    // ======================================================================
816
817    #[test]
818    fn set_single_property() {
819        let (tokens, input) = make_parser("SET n.name = 'Alice'");
820        let mut p = Parser::new(&tokens, &input);
821        let sc = p.parse_set_clause().expect("should parse");
822
823        assert_eq!(sc.items.len(), 1);
824        match &sc.items[0] {
825            SetItem::Property { target, value } => {
826                assert_eq!(
827                    *target,
828                    Expression::Property(
829                        Box::new(Expression::Variable("n".to_string())),
830                        "name".to_string(),
831                    )
832                );
833                assert_eq!(
834                    *value,
835                    Expression::Literal(Literal::String("Alice".to_string()))
836                );
837            }
838        }
839    }
840
841    #[test]
842    fn set_multiple_properties() {
843        let (tokens, input) = make_parser("SET n.name = 'Alice', n.age = 30");
844        let mut p = Parser::new(&tokens, &input);
845        let sc = p.parse_set_clause().expect("should parse");
846
847        assert_eq!(sc.items.len(), 2);
848    }
849
850    #[test]
851    fn remove_property() {
852        let (tokens, input) = make_parser("REMOVE n.email");
853        let mut p = Parser::new(&tokens, &input);
854        let rc = p.parse_remove_clause().expect("should parse");
855
856        assert_eq!(rc.items.len(), 1);
857        match &rc.items[0] {
858            RemoveItem::Property(expr) => {
859                assert_eq!(
860                    *expr,
861                    Expression::Property(
862                        Box::new(Expression::Variable("n".to_string())),
863                        "email".to_string(),
864                    )
865                );
866            }
867            _ => panic!("expected property remove"),
868        }
869    }
870
871    #[test]
872    fn remove_label() {
873        let (tokens, input) = make_parser("REMOVE n:Person");
874        let mut p = Parser::new(&tokens, &input);
875        let rc = p.parse_remove_clause().expect("should parse");
876
877        assert_eq!(rc.items.len(), 1);
878        match &rc.items[0] {
879            RemoveItem::Label { variable, label } => {
880                assert_eq!(variable, "n");
881                assert_eq!(label, "Person");
882            }
883            _ => panic!("expected label remove"),
884        }
885    }
886
887    #[test]
888    fn remove_multiple() {
889        let (tokens, input) = make_parser("REMOVE n.email, n:Temp");
890        let mut p = Parser::new(&tokens, &input);
891        let rc = p.parse_remove_clause().expect("should parse");
892
893        assert_eq!(rc.items.len(), 2);
894        assert!(matches!(&rc.items[0], RemoveItem::Property(_)));
895        assert!(matches!(&rc.items[1], RemoveItem::Label { .. }));
896    }
897
898    // ======================================================================
899    // TASK-033: parse_delete_clause
900    // ======================================================================
901
902    #[test]
903    fn delete_single() {
904        let (tokens, input) = make_parser("DELETE n");
905        let mut p = Parser::new(&tokens, &input);
906        let dc = p.parse_delete_clause(false).expect("should parse");
907
908        assert!(!dc.detach);
909        assert_eq!(dc.exprs.len(), 1);
910        assert_eq!(dc.exprs[0], Expression::Variable("n".to_string()));
911    }
912
913    #[test]
914    fn delete_multiple() {
915        let (tokens, input) = make_parser("DELETE n, m");
916        let mut p = Parser::new(&tokens, &input);
917        let dc = p.parse_delete_clause(false).expect("should parse");
918
919        assert_eq!(dc.exprs.len(), 2);
920    }
921
922    #[test]
923    fn delete_detach() {
924        let (tokens, input) = make_parser("DELETE n");
925        let mut p = Parser::new(&tokens, &input);
926        let dc = p.parse_delete_clause(true).expect("should parse");
927
928        assert!(dc.detach);
929    }
930
931    // ======================================================================
932    // WITH and MERGE (P2)
933    // ======================================================================
934
935    #[test]
936    fn with_simple() {
937        let (tokens, input) = make_parser("WITH n, m");
938        let mut p = Parser::new(&tokens, &input);
939        let wc = p.parse_with_clause().expect("should parse");
940
941        assert!(!wc.distinct);
942        assert_eq!(wc.items.len(), 2);
943        assert!(wc.where_clause.is_none());
944    }
945
946    #[test]
947    fn with_distinct_and_where() {
948        let (tokens, input) = make_parser("WITH DISTINCT n WHERE n.age > 30");
949        let mut p = Parser::new(&tokens, &input);
950        let wc = p.parse_with_clause().expect("should parse");
951
952        assert!(wc.distinct);
953        assert_eq!(wc.items.len(), 1);
954        assert!(wc.where_clause.is_some());
955    }
956
957    #[test]
958    fn merge_simple() {
959        let (tokens, input) = make_parser("MERGE (n:Person {name: 'Alice'})");
960        let mut p = Parser::new(&tokens, &input);
961        let mc = p.parse_merge_clause().expect("should parse");
962
963        let node = match &mc.pattern.chains[0].elements[0] {
964            PatternElement::Node(n) => n,
965            _ => panic!("expected node"),
966        };
967        assert_eq!(node.variable, Some("n".to_string()));
968        assert_eq!(node.labels, vec!["Person".to_string()]);
969        assert!(mc.on_match.is_empty());
970        assert!(mc.on_create.is_empty());
971    }
972
973    // TASK-084: MERGE with ON CREATE SET
974    #[test]
975    fn merge_on_create_set() {
976        let (tokens, input) =
977            make_parser("MERGE (n:Person {name: 'Alice'}) ON CREATE SET n.created = true");
978        let mut p = Parser::new(&tokens, &input);
979        let mc = p.parse_merge_clause().expect("should parse");
980
981        assert!(mc.on_match.is_empty());
982        assert_eq!(mc.on_create.len(), 1);
983        match &mc.on_create[0] {
984            SetItem::Property { target, value } => {
985                assert_eq!(
986                    *target,
987                    Expression::Property(
988                        Box::new(Expression::Variable("n".to_string())),
989                        "created".to_string(),
990                    )
991                );
992                assert_eq!(*value, Expression::Literal(Literal::Bool(true)));
993            }
994        }
995    }
996
997    // TASK-084: MERGE with ON MATCH SET
998    #[test]
999    fn merge_on_match_set() {
1000        let (tokens, input) =
1001            make_parser("MERGE (n:Person {name: 'Alice'}) ON MATCH SET n.seen = true");
1002        let mut p = Parser::new(&tokens, &input);
1003        let mc = p.parse_merge_clause().expect("should parse");
1004
1005        assert_eq!(mc.on_match.len(), 1);
1006        assert!(mc.on_create.is_empty());
1007    }
1008
1009    // TASK-084: MERGE with both ON CREATE SET and ON MATCH SET
1010    #[test]
1011    fn merge_on_create_and_on_match() {
1012        let (tokens, input) = make_parser(
1013            "MERGE (n:Person {name: 'Alice'}) ON CREATE SET n.created = true ON MATCH SET n.seen = true",
1014        );
1015        let mut p = Parser::new(&tokens, &input);
1016        let mc = p.parse_merge_clause().expect("should parse");
1017
1018        assert_eq!(mc.on_create.len(), 1);
1019        assert_eq!(mc.on_match.len(), 1);
1020    }
1021
1022    // TASK-084: MERGE with multiple SET items in ON CREATE
1023    #[test]
1024    fn merge_on_create_multiple_items() {
1025        let (tokens, input) = make_parser(
1026            "MERGE (n:Person {name: 'Alice'}) ON CREATE SET n.created = true, n.age = 1",
1027        );
1028        let mut p = Parser::new(&tokens, &input);
1029        let mc = p.parse_merge_clause().expect("should parse");
1030
1031        assert_eq!(mc.on_create.len(), 2);
1032    }
1033
1034    // ======================================================================
1035    // X-T3: AT TIME / BETWEEN TIME temporal predicate parsing
1036    // ======================================================================
1037
1038    #[test]
1039    fn match_at_time_literal() {
1040        let (tokens, input) = make_parser("MATCH (n:Person) AT TIME 1000 WHERE n.age > 30");
1041        let mut p = Parser::new(&tokens, &input);
1042        let mc = p.parse_match_clause(false).expect("should parse");
1043
1044        assert!(mc.temporal_predicate.is_some());
1045        match mc.temporal_predicate.as_ref().expect("checked above") {
1046            TemporalPredicate::AsOf(expr) => {
1047                assert_eq!(*expr, Expression::Literal(Literal::Integer(1000)));
1048            }
1049            _ => panic!("expected AsOf temporal predicate"),
1050        }
1051        assert!(mc.where_clause.is_some());
1052    }
1053
1054    #[test]
1055    fn match_at_time_function_call() {
1056        let (tokens, input) =
1057            make_parser("MATCH (n:Person) AT TIME datetime('2024-01-15T00:00:00Z')");
1058        let mut p = Parser::new(&tokens, &input);
1059        let mc = p.parse_match_clause(false).expect("should parse");
1060
1061        assert!(mc.temporal_predicate.is_some());
1062        match mc.temporal_predicate.as_ref().expect("checked above") {
1063            TemporalPredicate::AsOf(Expression::FunctionCall { name, .. }) => {
1064                assert_eq!(name, "datetime");
1065            }
1066            _ => panic!("expected AsOf with datetime function call"),
1067        }
1068    }
1069
1070    #[test]
1071    fn match_at_time_no_where() {
1072        let (tokens, input) = make_parser("MATCH (n:Person) AT TIME 1000");
1073        let mut p = Parser::new(&tokens, &input);
1074        let mc = p.parse_match_clause(false).expect("should parse");
1075
1076        assert!(mc.temporal_predicate.is_some());
1077        assert!(mc.where_clause.is_none());
1078    }
1079
1080    #[test]
1081    fn match_no_temporal_predicate() {
1082        let (tokens, input) = make_parser("MATCH (n:Person) WHERE n.age > 30");
1083        let mut p = Parser::new(&tokens, &input);
1084        let mc = p.parse_match_clause(false).expect("should parse");
1085
1086        assert!(mc.temporal_predicate.is_none());
1087        assert!(mc.where_clause.is_some());
1088    }
1089
1090    // Y-T1: BETWEEN TIME ... AND ... parsing
1091    #[test]
1092    fn match_between_time() {
1093        let (tokens, input) = make_parser("MATCH (n:Person) BETWEEN TIME 100 AND 200");
1094        let mut p = Parser::new(&tokens, &input);
1095        let mc = p.parse_match_clause(false).expect("should parse");
1096
1097        assert!(mc.temporal_predicate.is_some());
1098        match mc.temporal_predicate.as_ref().expect("checked above") {
1099            TemporalPredicate::Between(start, end) => {
1100                assert_eq!(*start, Expression::Literal(Literal::Integer(100)));
1101                assert_eq!(*end, Expression::Literal(Literal::Integer(200)));
1102            }
1103            _ => panic!("expected Between temporal predicate"),
1104        }
1105    }
1106
1107    #[test]
1108    fn match_between_time_with_where() {
1109        let (tokens, input) =
1110            make_parser("MATCH (n:Person) BETWEEN TIME 100 AND 200 WHERE n.age > 30");
1111        let mut p = Parser::new(&tokens, &input);
1112        let mc = p.parse_match_clause(false).expect("should parse");
1113
1114        assert!(mc.temporal_predicate.is_some());
1115        assert!(matches!(
1116            mc.temporal_predicate.as_ref().expect("checked"),
1117            TemporalPredicate::Between(_, _)
1118        ));
1119        assert!(mc.where_clause.is_some());
1120    }
1121
1122    // ======================================================================
1123    // TASK-068: parse_unwind_clause
1124    // ======================================================================
1125
1126    #[test]
1127    fn unwind_list_literal() {
1128        let (tokens, input) = make_parser("UNWIND [1, 2, 3] AS x");
1129        let mut p = Parser::new(&tokens, &input);
1130        let uc = p.parse_unwind_clause().expect("should parse");
1131
1132        assert_eq!(
1133            uc.expr,
1134            Expression::ListLiteral(vec![
1135                Expression::Literal(Literal::Integer(1)),
1136                Expression::Literal(Literal::Integer(2)),
1137                Expression::Literal(Literal::Integer(3)),
1138            ])
1139        );
1140        assert_eq!(uc.variable, "x");
1141    }
1142
1143    #[test]
1144    fn unwind_variable_expression() {
1145        let (tokens, input) = make_parser("UNWIND items AS item");
1146        let mut p = Parser::new(&tokens, &input);
1147        let uc = p.parse_unwind_clause().expect("should parse");
1148
1149        assert_eq!(uc.expr, Expression::Variable("items".to_string()));
1150        assert_eq!(uc.variable, "item");
1151    }
1152
1153    #[test]
1154    fn unwind_property_expression() {
1155        let (tokens, input) = make_parser("UNWIND n.hobbies AS h");
1156        let mut p = Parser::new(&tokens, &input);
1157        let uc = p.parse_unwind_clause().expect("should parse");
1158
1159        assert_eq!(
1160            uc.expr,
1161            Expression::Property(
1162                Box::new(Expression::Variable("n".to_string())),
1163                "hobbies".to_string(),
1164            )
1165        );
1166        assert_eq!(uc.variable, "h");
1167    }
1168
1169    #[test]
1170    fn unwind_empty_list() {
1171        let (tokens, input) = make_parser("UNWIND [] AS x");
1172        let mut p = Parser::new(&tokens, &input);
1173        let uc = p.parse_unwind_clause().expect("should parse");
1174
1175        assert_eq!(uc.expr, Expression::ListLiteral(vec![]));
1176        assert_eq!(uc.variable, "x");
1177    }
1178
1179    // ======================================================================
1180    // MM-001: parse_create_hyperedge_clause (cfg-gated)
1181    // ======================================================================
1182
1183    #[cfg(feature = "hypergraph")]
1184    mod hyperedge_tests {
1185        use super::*;
1186        use crate::parser::parse_query;
1187
1188        // MM-001: basic CREATE HYPEREDGE with FROM ... TO ...
1189        #[test]
1190        fn hyperedge_basic() {
1191            let (tokens, input) =
1192                make_parser("CREATE HYPEREDGE (h:GroupMigration) FROM (a, b) TO (c)");
1193            let mut p = Parser::new(&tokens, &input);
1194            p.advance(); // consume CREATE
1195            let hc = p.parse_create_hyperedge_clause().expect("should parse");
1196
1197            assert_eq!(hc.variable, Some("h".to_string()));
1198            assert_eq!(hc.labels, vec!["GroupMigration".to_string()]);
1199            assert_eq!(hc.sources.len(), 2);
1200            assert_eq!(hc.sources[0], Expression::Variable("a".to_string()));
1201            assert_eq!(hc.sources[1], Expression::Variable("b".to_string()));
1202            assert_eq!(hc.targets.len(), 1);
1203            assert_eq!(hc.targets[0], Expression::Variable("c".to_string()));
1204        }
1205
1206        // MM-002: CREATE HYPEREDGE with AT TIME temporal reference
1207        #[test]
1208        fn hyperedge_with_temporal_ref() {
1209            let (tokens, input) = make_parser(
1210                "CREATE HYPEREDGE (h:TemporalShift) FROM (person AT TIME 1000) TO (city2)",
1211            );
1212            let mut p = Parser::new(&tokens, &input);
1213            p.advance(); // consume CREATE
1214            let hc = p.parse_create_hyperedge_clause().expect("should parse");
1215
1216            assert_eq!(hc.variable, Some("h".to_string()));
1217            assert_eq!(hc.labels, vec!["TemporalShift".to_string()]);
1218            assert_eq!(hc.sources.len(), 1);
1219            // The source should be a TemporalRef expression
1220            assert!(matches!(&hc.sources[0], Expression::TemporalRef { .. }));
1221            if let Expression::TemporalRef { node, timestamp } = &hc.sources[0] {
1222                assert_eq!(**node, Expression::Variable("person".to_string()));
1223                assert_eq!(**timestamp, Expression::Literal(Literal::Integer(1000)));
1224            }
1225            assert_eq!(hc.targets.len(), 1);
1226        }
1227
1228        // MM-001: CREATE HYPEREDGE with no variable (anonymous)
1229        #[test]
1230        fn hyperedge_no_variable() {
1231            let (tokens, input) = make_parser("CREATE HYPEREDGE (:Migration) FROM (a) TO (b)");
1232            let mut p = Parser::new(&tokens, &input);
1233            p.advance(); // consume CREATE
1234            let hc = p.parse_create_hyperedge_clause().expect("should parse");
1235
1236            assert!(hc.variable.is_none());
1237            assert_eq!(hc.labels, vec!["Migration".to_string()]);
1238        }
1239
1240        // MM-001: CREATE HYPEREDGE with multiple labels
1241        #[test]
1242        fn hyperedge_multiple_sources_and_targets() {
1243            let (tokens, input) = make_parser("CREATE HYPEREDGE (h:Foo) FROM (a, b, c) TO (d, e)");
1244            let mut p = Parser::new(&tokens, &input);
1245            p.advance(); // consume CREATE
1246            let hc = p.parse_create_hyperedge_clause().expect("should parse");
1247
1248            assert_eq!(hc.sources.len(), 3);
1249            assert_eq!(hc.targets.len(), 2);
1250        }
1251
1252        // MM-003: MATCH HYPEREDGE basic
1253        #[test]
1254        fn match_hyperedge_basic() {
1255            let (tokens, input) = make_parser("MATCH HYPEREDGE (h:GroupMigration)");
1256            let mut p = Parser::new(&tokens, &input);
1257            p.advance(); // consume MATCH
1258            let mhc = p.parse_match_hyperedge_clause().expect("should parse");
1259
1260            assert_eq!(mhc.variable, Some("h".to_string()));
1261            assert_eq!(mhc.labels, vec!["GroupMigration".to_string()]);
1262        }
1263
1264        // MM-003: MATCH HYPEREDGE no label
1265        #[test]
1266        fn match_hyperedge_no_label() {
1267            let (tokens, input) = make_parser("MATCH HYPEREDGE (h)");
1268            let mut p = Parser::new(&tokens, &input);
1269            p.advance(); // consume MATCH
1270            let mhc = p.parse_match_hyperedge_clause().expect("should parse");
1271
1272            assert_eq!(mhc.variable, Some("h".to_string()));
1273            assert!(mhc.labels.is_empty());
1274        }
1275
1276        // MM-001: Integration test - full CREATE HYPEREDGE query
1277        #[test]
1278        fn query_create_hyperedge_full() {
1279            let q = parse_query("CREATE HYPEREDGE (h:GroupMigration) FROM (a, b) TO (c)")
1280                .expect("should parse");
1281            assert_eq!(q.clauses.len(), 1);
1282            assert!(matches!(&q.clauses[0], Clause::CreateHyperedge(_)));
1283        }
1284
1285        // MM-003: Integration test - full MATCH HYPEREDGE query
1286        #[test]
1287        fn query_match_hyperedge_full() {
1288            let q =
1289                parse_query("MATCH HYPEREDGE (h:GroupMigration) RETURN h").expect("should parse");
1290            assert_eq!(q.clauses.len(), 2);
1291            assert!(matches!(&q.clauses[0], Clause::MatchHyperedge(_)));
1292            assert!(matches!(&q.clauses[1], Clause::Return(_)));
1293        }
1294    }
1295
1296    // ======================================================================
1297    // HH-001: parse_create_snapshot_clause (cfg-gated)
1298    // ======================================================================
1299
1300    #[cfg(feature = "subgraph")]
1301    mod snapshot_tests {
1302        use super::*;
1303
1304        // HH-001: basic CREATE SNAPSHOT with FROM MATCH ... RETURN
1305        #[test]
1306        fn snapshot_basic() {
1307            let (tokens, input) =
1308                make_parser("CREATE SNAPSHOT (s:Snap) FROM MATCH (n:Person) RETURN n");
1309            let mut p = Parser::new(&tokens, &input);
1310            p.advance(); // consume CREATE
1311            let sc = p.parse_create_snapshot_clause().expect("should parse");
1312
1313            assert_eq!(sc.variable, Some("s".to_string()));
1314            assert_eq!(sc.labels, vec!["Snap".to_string()]);
1315            assert!(sc.properties.is_none());
1316            assert!(sc.temporal_anchor.is_none());
1317            // from_match should be a MATCH (n:Person) clause
1318            assert!(!sc.from_match.optional);
1319            assert_eq!(sc.from_match.pattern.chains.len(), 1);
1320            // from_return should have 1 item: n
1321            assert_eq!(sc.from_return.len(), 1);
1322            assert_eq!(
1323                sc.from_return[0].expr,
1324                Expression::Variable("n".to_string())
1325            );
1326        }
1327
1328        // HH-001: CREATE SNAPSHOT with properties
1329        #[test]
1330        fn snapshot_with_properties() {
1331            let (tokens, input) = make_parser(
1332                "CREATE SNAPSHOT (s:Snap {name: 'test'}) FROM MATCH (n:Person) RETURN n",
1333            );
1334            let mut p = Parser::new(&tokens, &input);
1335            p.advance(); // consume CREATE
1336            let sc = p.parse_create_snapshot_clause().expect("should parse");
1337
1338            assert_eq!(sc.variable, Some("s".to_string()));
1339            assert!(sc.properties.is_some());
1340            let props = sc.properties.expect("checked above");
1341            assert_eq!(props.len(), 1);
1342            assert_eq!(props[0].0, "name");
1343        }
1344
1345        // HH-001: CREATE SNAPSHOT with AT TIME
1346        #[test]
1347        fn snapshot_with_at_time() {
1348            let (tokens, input) =
1349                make_parser("CREATE SNAPSHOT (s:Snap) AT TIME 1000 FROM MATCH (n:Person) RETURN n");
1350            let mut p = Parser::new(&tokens, &input);
1351            p.advance(); // consume CREATE
1352            let sc = p.parse_create_snapshot_clause().expect("should parse");
1353
1354            assert!(sc.temporal_anchor.is_some());
1355            assert_eq!(
1356                sc.temporal_anchor.expect("checked"),
1357                Expression::Literal(Literal::Integer(1000))
1358            );
1359        }
1360
1361        // HH-001: CREATE SNAPSHOT with FROM MATCH ... WHERE ... RETURN
1362        #[test]
1363        fn snapshot_with_where() {
1364            let (tokens, input) = make_parser(
1365                "CREATE SNAPSHOT (s:Snap) FROM MATCH (n:Person) WHERE n.age > 30 RETURN n",
1366            );
1367            let mut p = Parser::new(&tokens, &input);
1368            p.advance(); // consume CREATE
1369            let sc = p.parse_create_snapshot_clause().expect("should parse");
1370
1371            assert!(sc.from_match.where_clause.is_some());
1372        }
1373
1374        // HH-001: CREATE SNAPSHOT with multiple RETURN items
1375        #[test]
1376        fn snapshot_multiple_return_items() {
1377            let (tokens, input) = make_parser(
1378                "CREATE SNAPSHOT (s:Snap) FROM MATCH (n:Person)-[:KNOWS]->(m) RETURN n, m",
1379            );
1380            let mut p = Parser::new(&tokens, &input);
1381            p.advance(); // consume CREATE
1382            let sc = p.parse_create_snapshot_clause().expect("should parse");
1383
1384            assert_eq!(sc.from_return.len(), 2);
1385        }
1386
1387        // HH-001: CREATE SNAPSHOT with no variable (anonymous snapshot)
1388        #[test]
1389        fn snapshot_no_variable() {
1390            let (tokens, input) =
1391                make_parser("CREATE SNAPSHOT (:Snap) FROM MATCH (n:Person) RETURN n");
1392            let mut p = Parser::new(&tokens, &input);
1393            p.advance(); // consume CREATE
1394            let sc = p.parse_create_snapshot_clause().expect("should parse");
1395
1396            assert!(sc.variable.is_none());
1397            assert_eq!(sc.labels, vec!["Snap".to_string()]);
1398        }
1399
1400        // HH-001: CREATE SNAPSHOT with AT TIME function call
1401        #[test]
1402        fn snapshot_at_time_function_call() {
1403            let (tokens, input) = make_parser(
1404                "CREATE SNAPSHOT (s:Snap) AT TIME datetime('2024-01-15T00:00:00Z') FROM MATCH (n:Person) RETURN n",
1405            );
1406            let mut p = Parser::new(&tokens, &input);
1407            p.advance(); // consume CREATE
1408            let sc = p.parse_create_snapshot_clause().expect("should parse");
1409
1410            assert!(sc.temporal_anchor.is_some());
1411            match sc.temporal_anchor.expect("checked") {
1412                Expression::FunctionCall { name, .. } => {
1413                    assert_eq!(name, "datetime");
1414                }
1415                _ => panic!("expected function call for temporal anchor"),
1416            }
1417        }
1418    }
1419}