Skip to main content

powdb_query/
parser.rs

1use crate::ast::*;
2use crate::lexer::lex;
3use crate::token::Token;
4
5/// Maximum nesting depth for recursive descent parsing.
6/// Prevents stack exhaustion from crafted inputs with deeply nested
7/// parentheses, subqueries, or CASE expressions.
8const MAX_NESTING_DEPTH: usize = 64;
9
10/// Discriminated parse error — callers can match on category.
11#[derive(Debug)]
12pub enum ParseError {
13    /// Lexer failed to tokenize the input.
14    Lex { message: String, position: usize },
15    /// Expected one token but found another.
16    UnexpectedToken { expected: String, got: String },
17    /// Recursive nesting exceeded the safety limit.
18    NestingDepthExceeded { max: usize },
19    /// Syntactically valid construct that the engine doesn't support yet.
20    Unsupported { feature: String },
21    /// Catch-all for other syntax errors.
22    Syntax { message: String },
23}
24
25impl ParseError {
26    /// Convenience: human-readable message for any variant.
27    pub fn message(&self) -> String {
28        self.to_string()
29    }
30}
31
32impl std::fmt::Display for ParseError {
33    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
34        match self {
35            Self::Lex { message, position } => write!(f, "at position {position}: {message}"),
36            Self::UnexpectedToken { expected, got } => {
37                write!(f, "expected {expected}, got {got}")
38            }
39            Self::NestingDepthExceeded { max } => {
40                write!(f, "query nesting depth exceeds maximum of {max}")
41            }
42            Self::Unsupported { feature } => write!(f, "{feature}"),
43            Self::Syntax { message } => write!(f, "{message}"),
44        }
45    }
46}
47
48impl std::error::Error for ParseError {}
49
50fn token_to_scalar_fn(tok: &Token) -> ScalarFn {
51    match tok {
52        Token::Upper => ScalarFn::Upper,
53        Token::Lower => ScalarFn::Lower,
54        Token::Length => ScalarFn::Length,
55        Token::Trim => ScalarFn::Trim,
56        Token::Substring => ScalarFn::Substring,
57        Token::Concat => ScalarFn::Concat,
58        Token::Abs => ScalarFn::Abs,
59        Token::Round => ScalarFn::Round,
60        Token::Ceil => ScalarFn::Ceil,
61        Token::Floor => ScalarFn::Floor,
62        Token::Sqrt => ScalarFn::Sqrt,
63        Token::Pow => ScalarFn::Pow,
64        Token::Now => ScalarFn::Now,
65        Token::Extract => ScalarFn::Extract,
66        Token::DateAdd => ScalarFn::DateAdd,
67        Token::DateDiff => ScalarFn::DateDiff,
68        _ => unreachable!(),
69    }
70}
71
72struct Parser {
73    tokens: Vec<Token>,
74    pos: usize,
75    depth: usize,
76}
77
78pub fn parse(input: &str) -> Result<Statement, ParseError> {
79    let tokens = lex(input).map_err(|e| ParseError::Lex {
80        message: e.message,
81        position: e.position,
82    })?;
83    let mut parser = Parser {
84        tokens,
85        pos: 0,
86        depth: 0,
87    };
88    let stmt = parser.parse_statement()?;
89    // Reject trailing tokens. Without this, unrecognized tails like
90    // `User create_index .email` silently succeed as `User` — which
91    // misled the TS client into thinking those non-existent DDL forms
92    // returned rows. A parse error here tells users that the syntax
93    // they wrote isn't recognized.
94    if !matches!(parser.peek(), Token::Eof) {
95        return Err(ParseError::Syntax {
96            message: format!(
97                "unexpected trailing token: {}",
98                parser.peek().display_name()
99            ),
100        });
101    }
102    Ok(stmt)
103}
104
105/// Rewrite `Field(alias)` references inside `expr` to the underlying
106/// projection expression they alias. Used to desugar post-projection HAVING
107/// (`{ ..., cnt: count(.name) } having cnt >= 2`) into a form the planner's
108/// aggregate extraction can handle.
109fn substitute_projection_aliases(expr: Expr, fields: &[ProjectionField]) -> Expr {
110    match expr {
111        Expr::Field(ref name) => {
112            for f in fields {
113                if f.alias.as_deref() == Some(name.as_str()) {
114                    return f.expr.clone();
115                }
116            }
117            expr
118        }
119        Expr::BinaryOp(l, op, r) => Expr::BinaryOp(
120            Box::new(substitute_projection_aliases(*l, fields)),
121            op,
122            Box::new(substitute_projection_aliases(*r, fields)),
123        ),
124        Expr::UnaryOp(op, inner) => {
125            Expr::UnaryOp(op, Box::new(substitute_projection_aliases(*inner, fields)))
126        }
127        Expr::Coalesce(l, r) => Expr::Coalesce(
128            Box::new(substitute_projection_aliases(*l, fields)),
129            Box::new(substitute_projection_aliases(*r, fields)),
130        ),
131        Expr::InList {
132            expr: e,
133            list,
134            negated,
135        } => Expr::InList {
136            expr: Box::new(substitute_projection_aliases(*e, fields)),
137            list: list
138                .into_iter()
139                .map(|i| substitute_projection_aliases(i, fields))
140                .collect(),
141            negated,
142        },
143        Expr::ScalarFunc(f, args) => Expr::ScalarFunc(
144            f,
145            args.into_iter()
146                .map(|a| substitute_projection_aliases(a, fields))
147                .collect(),
148        ),
149        other => other,
150    }
151}
152
153impl Parser {
154    fn peek(&self) -> &Token {
155        &self.tokens[self.pos]
156    }
157
158    fn advance(&mut self) -> Token {
159        let t = self.tokens[self.pos].clone();
160        self.pos += 1;
161        t
162    }
163
164    fn expect(&mut self, expected: &Token) -> Result<(), ParseError> {
165        let t = self.advance();
166        if &t == expected {
167            Ok(())
168        } else {
169            Err(ParseError::UnexpectedToken {
170                expected: expected.display_name(),
171                got: t.display_name(),
172            })
173        }
174    }
175
176    /// Convenience: create an UnexpectedToken error.
177    fn unexpected(&self, expected: &str, got: &Token) -> ParseError {
178        ParseError::UnexpectedToken {
179            expected: expected.into(),
180            got: got.display_name(),
181        }
182    }
183
184    fn parse_statement(&mut self) -> Result<Statement, ParseError> {
185        self.depth += 1;
186        if self.depth > MAX_NESTING_DEPTH {
187            self.depth -= 1;
188            return Err(ParseError::NestingDepthExceeded {
189                max: MAX_NESTING_DEPTH,
190            });
191        }
192        if matches!(self.peek(), Token::Explain) {
193            self.advance();
194            let inner = self.parse_statement()?;
195            self.depth -= 1;
196            return Ok(Statement::Explain(Box::new(inner)));
197        }
198        let stmt = match self.peek() {
199            Token::Insert => self.parse_insert(),
200            Token::Upsert => self.parse_upsert(),
201            Token::Type => self.parse_create_type(),
202            Token::Alter => self.parse_alter_table(),
203            Token::Drop => self.parse_drop_or_drop_view(),
204            Token::Materialized => self.parse_create_view(),
205            Token::Refresh => self.parse_refresh_view(),
206            Token::Count | Token::Avg | Token::Sum | Token::Min | Token::Max => {
207                self.parse_aggregate_query()
208            }
209            Token::Ident(_) => self.parse_query_or_mutation(),
210            _ => Err(self.unexpected("statement", self.peek())),
211        }?;
212        // Check for UNION chaining after any query-producing statement.
213        let result = self.maybe_parse_union(stmt);
214        self.depth -= 1;
215        result
216    }
217
218    fn parse_query_or_mutation(&mut self) -> Result<Statement, ParseError> {
219        let source = match self.advance() {
220            Token::Ident(name) => name,
221            t => {
222                return Err(ParseError::UnexpectedToken {
223                    expected: "type name".into(),
224                    got: t.display_name(),
225                })
226            }
227        };
228        let alias = self.try_parse_alias();
229        let joins = self.parse_joins()?;
230
231        // Walk filter/order/limit/offset/projection, peeling off update/delete
232        // mutations as we hit them. Anything else terminates the read pipeline
233        // and we return a Query.
234        let mut filter = None;
235        let mut order = None;
236        let mut limit = None;
237        let mut offset = None;
238        let mut projection = None;
239        let mut distinct = false;
240        let mut group_by = None;
241
242        loop {
243            match self.peek() {
244                Token::Distinct => {
245                    self.advance();
246                    distinct = true;
247                }
248                Token::Group => {
249                    self.advance();
250                    group_by = Some(self.parse_group_by()?);
251                }
252                Token::Filter => {
253                    self.advance();
254                    filter = Some(self.parse_expr()?);
255                }
256                Token::Order => {
257                    self.advance();
258                    order = Some(self.parse_order()?);
259                }
260                Token::Limit => {
261                    self.advance();
262                    limit = Some(self.parse_expr()?);
263                }
264                Token::Offset => {
265                    self.advance();
266                    offset = Some(self.parse_expr()?);
267                }
268                Token::LBrace => {
269                    projection = Some(self.parse_projection()?);
270                }
271                Token::Having => {
272                    // Post-projection HAVING — see parse_query_tail for details.
273                    self.advance();
274                    let having_expr = self.parse_expr()?;
275                    let group = group_by.as_mut().ok_or_else(|| ParseError::Syntax {
276                        message: "having without group by".into(),
277                    })?;
278                    let rewritten = match projection.as_ref() {
279                        Some(fields) => substitute_projection_aliases(having_expr, fields),
280                        None => having_expr,
281                    };
282                    group.having = Some(match group.having.take() {
283                        Some(existing) => {
284                            Expr::BinaryOp(Box::new(existing), BinOp::And, Box::new(rewritten))
285                        }
286                        None => rewritten,
287                    });
288                }
289                Token::Update => {
290                    if !joins.is_empty() {
291                        return Err(ParseError::Unsupported {
292                            feature: "update on a joined query is not supported".into(),
293                        });
294                    }
295                    self.advance();
296                    let assignments = self.parse_assignments()?;
297                    return Ok(Statement::UpdateQuery(UpdateExpr {
298                        source,
299                        filter,
300                        assignments,
301                    }));
302                }
303                Token::Delete => {
304                    if !joins.is_empty() {
305                        return Err(ParseError::Unsupported {
306                            feature: "delete on a joined query is not supported".into(),
307                        });
308                    }
309                    self.advance();
310                    return Ok(Statement::DeleteQuery(DeleteExpr { source, filter }));
311                }
312                _ => break,
313            }
314        }
315
316        Ok(Statement::Query(QueryExpr {
317            source,
318            alias,
319            joins,
320            filter,
321            order,
322            limit,
323            offset,
324            projection,
325            aggregation: None,
326            distinct,
327            group_by,
328        }))
329    }
330
331    /// Parse the read-only tail of a query (filter/order/limit/offset/projection)
332    /// after `source` has already been consumed. Stops at the first token that
333    /// isn't part of a read pipeline — the caller decides whether that's a
334    /// terminator (RParen for an aggregate, EOF for a top-level query, etc.).
335    /// Always returns `aggregation: None`; the caller layers that on.
336    fn parse_query_tail(&mut self, source: String) -> Result<QueryExpr, ParseError> {
337        let alias = self.try_parse_alias();
338        let joins = self.parse_joins()?;
339        let mut filter = None;
340        let mut order = None;
341        let mut limit = None;
342        let mut offset = None;
343        let mut projection = None;
344        let mut distinct = false;
345        let mut group_by = None;
346
347        loop {
348            match self.peek() {
349                Token::Distinct => {
350                    self.advance();
351                    distinct = true;
352                }
353                Token::Group => {
354                    self.advance();
355                    group_by = Some(self.parse_group_by()?);
356                }
357                Token::Filter => {
358                    self.advance();
359                    filter = Some(self.parse_expr()?);
360                }
361                Token::Order => {
362                    self.advance();
363                    order = Some(self.parse_order()?);
364                }
365                Token::Limit => {
366                    self.advance();
367                    limit = Some(self.parse_expr()?);
368                }
369                Token::Offset => {
370                    self.advance();
371                    offset = Some(self.parse_expr()?);
372                }
373                Token::LBrace => {
374                    projection = Some(self.parse_projection()?);
375                }
376                Token::Having => {
377                    // Post-projection HAVING — `... group .k { .k, cnt: count(.name) } having cnt >= 2`.
378                    // Only meaningful when a GROUP BY is present. We desugar
379                    // to a regular HAVING on the GroupByClause, rewriting
380                    // projection aliases back into their underlying expressions
381                    // so the planner's extract_aggregates can dedup them.
382                    self.advance();
383                    let having_expr = self.parse_expr()?;
384                    let group = group_by.as_mut().ok_or_else(|| ParseError::Syntax {
385                        message: "having without group by".into(),
386                    })?;
387                    let rewritten = match projection.as_ref() {
388                        Some(fields) => substitute_projection_aliases(having_expr, fields),
389                        None => having_expr,
390                    };
391                    group.having = Some(match group.having.take() {
392                        Some(existing) => {
393                            Expr::BinaryOp(Box::new(existing), BinOp::And, Box::new(rewritten))
394                        }
395                        None => rewritten,
396                    });
397                }
398                _ => break,
399            }
400        }
401
402        Ok(QueryExpr {
403            source,
404            alias,
405            joins,
406            filter,
407            order,
408            limit,
409            offset,
410            projection,
411            aggregation: None,
412            distinct,
413            group_by,
414        })
415    }
416
417    /// Consume an optional `as <ident>` suffix on a source. Returns `None`
418    /// if the next token isn't `as`. Used by both the primary source and each
419    /// join source so queries can disambiguate columns via `alias.field`.
420    fn try_parse_alias(&mut self) -> Option<String> {
421        if *self.peek() == Token::As {
422            self.advance();
423            if let Token::Ident(name) = self.peek().clone() {
424                self.advance();
425                return Some(name);
426            }
427        }
428        None
429    }
430
431    /// Parse zero or more join clauses. Each clause is:
432    ///   (`inner` | `left` [`outer`] | `right` [`outer`] | `cross`)? `join`
433    ///   <Ident> [`as` <ident>] [`on` <expr>]
434    ///
435    /// `on` is required for every kind except `cross`. The default kind is
436    /// `inner` when the caller wrote bare `join` without a preceding modifier.
437    fn parse_joins(&mut self) -> Result<Vec<JoinClause>, ParseError> {
438        let mut joins = Vec::new();
439        loop {
440            let kind = match self.peek() {
441                Token::Join => {
442                    self.advance();
443                    JoinKind::Inner
444                }
445                Token::Inner => {
446                    self.advance();
447                    self.expect(&Token::Join)?;
448                    JoinKind::Inner
449                }
450                Token::LeftKw => {
451                    self.advance();
452                    if *self.peek() == Token::Outer {
453                        self.advance();
454                    }
455                    self.expect(&Token::Join)?;
456                    JoinKind::LeftOuter
457                }
458                Token::RightKw => {
459                    self.advance();
460                    if *self.peek() == Token::Outer {
461                        self.advance();
462                    }
463                    self.expect(&Token::Join)?;
464                    JoinKind::RightOuter
465                }
466                Token::Cross => {
467                    self.advance();
468                    self.expect(&Token::Join)?;
469                    JoinKind::Cross
470                }
471                _ => break,
472            };
473
474            let source = match self.advance() {
475                Token::Ident(name) => name,
476                t => {
477                    return Err(ParseError::UnexpectedToken {
478                        expected: "type name after join".into(),
479                        got: t.display_name(),
480                    });
481                }
482            };
483            let alias = self.try_parse_alias();
484            let on = if kind == JoinKind::Cross {
485                None
486            } else if *self.peek() == Token::On {
487                self.advance();
488                Some(self.parse_expr()?)
489            } else {
490                return Err(ParseError::Syntax {
491                    message: format!("expected `on <expr>` after join {source}"),
492                });
493            };
494
495            joins.push(JoinClause {
496                kind,
497                source,
498                alias,
499                on,
500            });
501        }
502        Ok(joins)
503    }
504
505    fn parse_insert(&mut self) -> Result<Statement, ParseError> {
506        self.expect(&Token::Insert)?;
507        let target = match self.advance() {
508            Token::Ident(name) => name,
509            t => {
510                return Err(ParseError::UnexpectedToken {
511                    expected: "type name".into(),
512                    got: t.display_name(),
513                })
514            }
515        };
516        let assignments = self.parse_assignments()?;
517        Ok(Statement::Insert(InsertExpr {
518            target,
519            assignments,
520        }))
521    }
522
523    /// Parse: `upsert Table on .key_col { assignments } [on conflict { update_assignments }]`
524    fn parse_upsert(&mut self) -> Result<Statement, ParseError> {
525        self.expect(&Token::Upsert)?;
526        let target = match self.advance() {
527            Token::Ident(name) => name,
528            t => {
529                return Err(ParseError::UnexpectedToken {
530                    expected: "type name".into(),
531                    got: t.display_name(),
532                })
533            }
534        };
535        self.expect(&Token::On)?;
536        let key_column = match self.advance() {
537            Token::DotIdent(name) => name,
538            t => {
539                return Err(ParseError::UnexpectedToken {
540                    expected: ".key_column".into(),
541                    got: t.display_name(),
542                })
543            }
544        };
545        let assignments = self.parse_assignments()?;
546        let on_conflict = if *self.peek() == Token::On {
547            self.advance(); // consume `on`
548            self.expect(&Token::Conflict)?;
549            self.parse_assignments()?
550        } else {
551            Vec::new()
552        };
553        Ok(Statement::Upsert(UpsertExpr {
554            target,
555            key_column,
556            assignments,
557            on_conflict,
558        }))
559    }
560
561    fn parse_assignments(&mut self) -> Result<Vec<Assignment>, ParseError> {
562        self.expect(&Token::LBrace)?;
563        let mut assignments = Vec::new();
564        while !matches!(self.peek(), Token::RBrace | Token::Eof) {
565            let field = match self.advance() {
566                Token::Ident(name) => name,
567                t => {
568                    return Err(ParseError::UnexpectedToken {
569                        expected: "field name".into(),
570                        got: t.display_name(),
571                    })
572                }
573            };
574            self.expect(&Token::Assign)?;
575            let value = self.parse_expr()?;
576            assignments.push(Assignment { field, value });
577            if *self.peek() == Token::Comma {
578                self.advance();
579            }
580        }
581        self.expect(&Token::RBrace)?;
582        Ok(assignments)
583    }
584
585    fn parse_projection(&mut self) -> Result<Vec<ProjectionField>, ParseError> {
586        self.expect(&Token::LBrace)?;
587        let mut fields = Vec::new();
588        while !matches!(self.peek(), Token::RBrace | Token::Eof) {
589            let first = self.advance();
590            if *self.peek() == Token::Colon {
591                // alias: expr
592                self.advance();
593                let alias = match first {
594                    Token::Ident(name) => name,
595                    _ => {
596                        return Err(ParseError::Syntax {
597                            message: "expected alias name".into(),
598                        })
599                    }
600                };
601                let expr = self.parse_expr()?;
602                fields.push(ProjectionField {
603                    alias: Some(alias),
604                    expr,
605                });
606            } else {
607                let expr = match first {
608                    // Mission E1.2: `{ u.name }` — a qualifier followed by
609                    // `.field` folds into a QualifiedField so join projections
610                    // can pull from a specific source.
611                    Token::Ident(name) => {
612                        if let Token::DotIdent(field) = self.peek().clone() {
613                            self.advance();
614                            Expr::QualifiedField {
615                                qualifier: name,
616                                field,
617                            }
618                        } else {
619                            Expr::Field(name)
620                        }
621                    }
622                    Token::DotIdent(name) => Expr::Field(name),
623                    Token::RowNumber | Token::Rank | Token::DenseRank => {
624                        let wfunc = match first {
625                            Token::RowNumber => WindowFunc::RowNumber,
626                            Token::Rank => WindowFunc::Rank,
627                            Token::DenseRank => WindowFunc::DenseRank,
628                            _ => {
629                                return Err(ParseError::Syntax {
630                                    message: "unexpected window function token".into(),
631                                })
632                            }
633                        };
634                        self.expect(&Token::LParen)?;
635                        self.expect(&Token::RParen)?;
636                        let (partition_by, order_by) = self.parse_over_clause()?;
637                        Expr::Window {
638                            function: wfunc,
639                            args: vec![],
640                            partition_by,
641                            order_by,
642                        }
643                    }
644                    Token::Count | Token::Avg | Token::Sum | Token::Min | Token::Max => {
645                        let mut func = match first {
646                            Token::Count => AggFunc::Count,
647                            Token::Avg => AggFunc::Avg,
648                            Token::Sum => AggFunc::Sum,
649                            Token::Min => AggFunc::Min,
650                            Token::Max => AggFunc::Max,
651                            _ => {
652                                return Err(ParseError::Syntax {
653                                    message: "unexpected aggregate token".into(),
654                                })
655                            }
656                        };
657                        self.expect(&Token::LParen)?;
658                        // count(*) — count all rows
659                        if func == AggFunc::Count && *self.peek() == Token::Star {
660                            self.advance();
661                            self.expect(&Token::RParen)?;
662                            // Check for OVER — count(*) over (...)
663                            if *self.peek() == Token::Over {
664                                let (partition_by, order_by) = self.parse_over_clause()?;
665                                Expr::Window {
666                                    function: WindowFunc::Count,
667                                    args: vec![Expr::Field("*".into())],
668                                    partition_by,
669                                    order_by,
670                                }
671                            } else {
672                                Expr::FunctionCall(
673                                    AggFunc::Count,
674                                    Box::new(Expr::Field("*".into())),
675                                )
676                            }
677                        } else {
678                            // count(distinct .field) → CountDistinct
679                            if func == AggFunc::Count && *self.peek() == Token::Distinct {
680                                self.advance();
681                                func = AggFunc::CountDistinct;
682                            }
683                            let inner = self.parse_expr()?;
684                            self.expect(&Token::RParen)?;
685                            // Check for OVER — e.g. sum(.salary) over (...)
686                            if *self.peek() == Token::Over {
687                                let wfunc =
688                                    match func {
689                                        AggFunc::Count => WindowFunc::Count,
690                                        AggFunc::Avg => WindowFunc::Avg,
691                                        AggFunc::Sum => WindowFunc::Sum,
692                                        AggFunc::Min => WindowFunc::Min,
693                                        AggFunc::Max => WindowFunc::Max,
694                                        _ => return Err(ParseError::Unsupported {
695                                            feature:
696                                                "count(distinct ...) over (...) is not supported"
697                                                    .into(),
698                                        }),
699                                    };
700                                let (partition_by, order_by) = self.parse_over_clause()?;
701                                Expr::Window {
702                                    function: wfunc,
703                                    args: vec![inner],
704                                    partition_by,
705                                    order_by,
706                                }
707                            } else {
708                                Expr::FunctionCall(func, Box::new(inner))
709                            }
710                        }
711                    }
712                    Token::Upper
713                    | Token::Lower
714                    | Token::Length
715                    | Token::Trim
716                    | Token::Substring
717                    | Token::Concat
718                    | Token::Abs
719                    | Token::Round
720                    | Token::Ceil
721                    | Token::Floor
722                    | Token::Sqrt
723                    | Token::Pow
724                    | Token::Now
725                    | Token::Extract
726                    | Token::DateAdd
727                    | Token::DateDiff => {
728                        let func = token_to_scalar_fn(&first);
729                        self.expect(&Token::LParen)?;
730                        let mut args = Vec::new();
731                        while !matches!(self.peek(), Token::RParen | Token::Eof) {
732                            args.push(self.parse_expr()?);
733                            if *self.peek() == Token::Comma {
734                                self.advance();
735                            }
736                        }
737                        self.expect(&Token::RParen)?;
738                        Expr::ScalarFunc(func, args)
739                    }
740                    Token::Cast => {
741                        self.expect(&Token::LParen)?;
742                        let inner = self.parse_expr()?;
743                        self.expect(&Token::Comma)?;
744                        let cast_type = self.parse_cast_type()?;
745                        self.expect(&Token::RParen)?;
746                        Expr::Cast(Box::new(inner), cast_type)
747                    }
748                    Token::Case => {
749                        let mut whens = Vec::new();
750                        while *self.peek() == Token::When {
751                            self.advance();
752                            let condition = self.parse_expr()?;
753                            self.expect(&Token::Then)?;
754                            let result = self.parse_expr()?;
755                            whens.push((Box::new(condition), Box::new(result)));
756                        }
757                        let else_expr = if *self.peek() == Token::Else {
758                            self.advance();
759                            Some(Box::new(self.parse_expr()?))
760                        } else {
761                            None
762                        };
763                        self.expect(&Token::End)?;
764                        Expr::Case { whens, else_expr }
765                    }
766                    _ => {
767                        return Err(ParseError::UnexpectedToken {
768                            expected: "field".into(),
769                            got: first.display_name(),
770                        })
771                    }
772                };
773                fields.push(ProjectionField { alias: None, expr });
774            }
775            if *self.peek() == Token::Comma {
776                self.advance();
777            }
778        }
779        self.expect(&Token::RBrace)?;
780        Ok(fields)
781    }
782
783    /// Parse the OVER clause for a window function:
784    /// `over (partition .col1, .col2 order .col3 asc, .col4 desc)`
785    fn parse_over_clause(&mut self) -> Result<(Vec<String>, Vec<OrderKey>), ParseError> {
786        self.expect(&Token::Over)?;
787        self.expect(&Token::LParen)?;
788        let mut partition_by = Vec::new();
789        let mut order_by = Vec::new();
790        if *self.peek() == Token::Partition {
791            self.advance();
792            while let Token::DotIdent(name) = self.peek() {
793                let name = name.clone();
794                self.advance();
795                partition_by.push(name);
796                if *self.peek() == Token::Comma {
797                    // Only consume comma if the next token is another DotIdent
798                    // (i.e. still in the partition list). If the next meaningful
799                    // token is `order`, `RParen`, etc., stop.
800                    if matches!(self.tokens.get(self.pos + 1), Some(Token::DotIdent(_))) {
801                        self.advance();
802                    } else {
803                        break;
804                    }
805                } else {
806                    break;
807                }
808            }
809        }
810        if *self.peek() == Token::Order {
811            self.advance();
812            while let Token::DotIdent(name) = self.peek() {
813                let field = name.clone();
814                self.advance();
815                let descending = match self.peek() {
816                    Token::Desc => {
817                        self.advance();
818                        true
819                    }
820                    Token::Asc => {
821                        self.advance();
822                        false
823                    }
824                    _ => false,
825                };
826                order_by.push(OrderKey { field, descending });
827                if *self.peek() == Token::Comma {
828                    self.advance();
829                } else {
830                    break;
831                }
832            }
833        }
834        self.expect(&Token::RParen)?;
835        Ok((partition_by, order_by))
836    }
837
838    /// Parse a cast target type from a string literal: `"int"`, `"float"`, `"str"`, `"bool"`, `"datetime"`.
839    fn parse_cast_type(&mut self) -> Result<CastType, ParseError> {
840        match self.advance() {
841            Token::StringLit(s) => match s.as_str() {
842                "int" | "Int" | "INT" => Ok(CastType::Int),
843                "float" | "Float" | "FLOAT" => Ok(CastType::Float),
844                "str" | "Str" | "STR" | "string" | "String" => Ok(CastType::Str),
845                "bool" | "Bool" | "BOOL" | "boolean" => Ok(CastType::Bool),
846                "datetime" | "DateTime" | "DATETIME" => Ok(CastType::DateTime),
847                other => Err(ParseError::Syntax {
848                    message: format!("invalid cast type: \"{other}\""),
849                }),
850            },
851            t => Err(ParseError::UnexpectedToken {
852                expected: "string literal for cast type".into(),
853                got: t.display_name(),
854            }),
855        }
856    }
857
858    fn parse_order(&mut self) -> Result<OrderClause, ParseError> {
859        let mut keys = Vec::new();
860        loop {
861            let field = match self.advance() {
862                Token::DotIdent(name) => name,
863                t => {
864                    return Err(ParseError::UnexpectedToken {
865                        expected: ".field after order".into(),
866                        got: t.display_name(),
867                    })
868                }
869            };
870            let descending = match self.peek() {
871                Token::Desc => {
872                    self.advance();
873                    true
874                }
875                Token::Asc => {
876                    self.advance();
877                    false
878                }
879                _ => false,
880            };
881            keys.push(OrderKey { field, descending });
882            if *self.peek() == Token::Comma {
883                self.advance();
884            } else {
885                break;
886            }
887        }
888        Ok(OrderClause { keys })
889    }
890
891    fn parse_aggregate_query(&mut self) -> Result<Statement, ParseError> {
892        let mut func = match self.advance() {
893            Token::Count => AggFunc::Count,
894            Token::Avg => AggFunc::Avg,
895            Token::Sum => AggFunc::Sum,
896            Token::Min => AggFunc::Min,
897            Token::Max => AggFunc::Max,
898            t => {
899                return Err(ParseError::UnexpectedToken {
900                    expected: "aggregate function".into(),
901                    got: t.display_name(),
902                })
903            }
904        };
905        self.expect(&Token::LParen)?;
906        // count(distinct User ...) → CountDistinct
907        if func == AggFunc::Count && *self.peek() == Token::Distinct {
908            self.advance();
909            func = AggFunc::CountDistinct;
910        }
911        let source = match self.advance() {
912            Token::Ident(name) => name,
913            t => {
914                return Err(ParseError::UnexpectedToken {
915                    expected: "type name".into(),
916                    got: t.display_name(),
917                })
918            }
919        };
920        // Allow a full read-pipeline tail inside the parens, e.g.
921        // `count(User filter .age > 27 limit 100)`. parse_query_tail stops at
922        // the first non-pipeline token, which here must be RParen.
923        let mut query = self.parse_query_tail(source)?;
924        self.expect(&Token::RParen)?;
925
926        // For non-count aggregates (and count distinct), the caller typically
927        // writes the target column via the trailing projection form:
928        //     sum(User filter .age > 30 { .age })
929        //     count(distinct User { .name })
930        // We lift that single unaliased `.field` into AggregateExpr.field so
931        // the executor's aggregate fast paths can see it.
932        let mut agg_field: Option<String> = None;
933        if func != AggFunc::Count {
934            if let Some(proj) = &query.projection {
935                if proj.len() == 1 && proj[0].alias.is_none() {
936                    if let Expr::Field(name) = &proj[0].expr {
937                        agg_field = Some(name.clone());
938                    }
939                }
940            }
941            if agg_field.is_some() {
942                query.projection = None;
943            }
944        }
945        query.aggregation = Some(AggregateExpr {
946            function: func,
947            field: agg_field,
948        });
949        Ok(Statement::Query(query))
950    }
951
952    fn parse_expr(&mut self) -> Result<Expr, ParseError> {
953        self.depth += 1;
954        if self.depth > MAX_NESTING_DEPTH {
955            self.depth -= 1;
956            return Err(ParseError::NestingDepthExceeded {
957                max: MAX_NESTING_DEPTH,
958            });
959        }
960        let result = self.parse_or_expr();
961        self.depth -= 1;
962        result
963    }
964
965    fn parse_or_expr(&mut self) -> Result<Expr, ParseError> {
966        let mut left = self.parse_and_expr()?;
967        while *self.peek() == Token::Or {
968            self.advance();
969            let right = self.parse_and_expr()?;
970            left = Expr::BinaryOp(Box::new(left), BinOp::Or, Box::new(right));
971        }
972        Ok(left)
973    }
974
975    fn parse_and_expr(&mut self) -> Result<Expr, ParseError> {
976        let mut left = self.parse_comparison()?;
977        while *self.peek() == Token::And {
978            self.advance();
979            let right = self.parse_comparison()?;
980            left = Expr::BinaryOp(Box::new(left), BinOp::And, Box::new(right));
981        }
982        Ok(left)
983    }
984
985    fn parse_comparison(&mut self) -> Result<Expr, ParseError> {
986        let left = self.parse_additive()?;
987
988        // IS NULL / IS NOT NULL (postfix)
989        if *self.peek() == Token::Is {
990            self.advance();
991            if *self.peek() == Token::Not {
992                self.advance();
993                self.expect(&Token::Null)?;
994                return Ok(Expr::UnaryOp(UnaryOp::IsNotNull, Box::new(left)));
995            } else {
996                self.expect(&Token::Null)?;
997                return Ok(Expr::UnaryOp(UnaryOp::IsNull, Box::new(left)));
998            }
999        }
1000
1001        // Postfix: `in (...)`, `like "..."`, `between X and Y`
1002        // and their negated forms: `not in`, `not like`, `not between`.
1003        match self.peek() {
1004            Token::In => {
1005                self.advance();
1006                return self.parse_in_list(left, false);
1007            }
1008            Token::Like => {
1009                self.advance();
1010                let pattern = self.parse_additive()?;
1011                return Ok(Expr::BinaryOp(
1012                    Box::new(left),
1013                    BinOp::Like,
1014                    Box::new(pattern),
1015                ));
1016            }
1017            Token::Between => {
1018                self.advance();
1019                return self.parse_between(left, false);
1020            }
1021            Token::Not => {
1022                // Peek ahead: `not in`, `not like`, `not between`.
1023                // If the token after `not` isn't one of these, don't consume
1024                // `not` — let the caller handle it.
1025                let next = self.tokens.get(self.pos + 1);
1026                match next {
1027                    Some(Token::In) => {
1028                        self.advance(); // not
1029                        self.advance(); // in
1030                        return self.parse_in_list(left, true);
1031                    }
1032                    Some(Token::Like) => {
1033                        self.advance(); // not
1034                        self.advance(); // like
1035                        let pattern = self.parse_additive()?;
1036                        let like = Expr::BinaryOp(Box::new(left), BinOp::Like, Box::new(pattern));
1037                        return Ok(Expr::UnaryOp(UnaryOp::Not, Box::new(like)));
1038                    }
1039                    Some(Token::Between) => {
1040                        self.advance(); // not
1041                        self.advance(); // between
1042                        return self.parse_between(left, true);
1043                    }
1044                    _ => {}
1045                }
1046            }
1047            _ => {}
1048        }
1049
1050        let op = match self.peek() {
1051            Token::Eq => BinOp::Eq,
1052            Token::Neq => BinOp::Neq,
1053            Token::Lt => BinOp::Lt,
1054            Token::Gt => BinOp::Gt,
1055            Token::Lte => BinOp::Lte,
1056            Token::Gte => BinOp::Gte,
1057            _ => return Ok(left),
1058        };
1059        self.advance();
1060        // `expr = null` / `expr != null` desugar to the same UnaryOp as
1061        // `expr is null` / `expr is not null`. Ordering comparisons against
1062        // null (`< null`, `>= null`, etc.) remain parse errors.
1063        if *self.peek() == Token::Null {
1064            match op {
1065                BinOp::Eq => {
1066                    self.advance();
1067                    return Ok(Expr::UnaryOp(UnaryOp::IsNull, Box::new(left)));
1068                }
1069                BinOp::Neq => {
1070                    self.advance();
1071                    return Ok(Expr::UnaryOp(UnaryOp::IsNotNull, Box::new(left)));
1072                }
1073                _ => {}
1074            }
1075        }
1076        let right = self.parse_additive()?;
1077        Ok(Expr::BinaryOp(Box::new(left), op, Box::new(right)))
1078    }
1079
1080    /// Parse `(val1, val2, ...)` or `(subquery)` after `in` / `not in`.
1081    /// A subquery is detected by `(` followed by an `Ident` that is NOT
1082    /// followed by `,` or `)` — in PowQL, bare identifiers in value lists
1083    /// don't appear (field refs start with `.`).
1084    fn parse_in_list(&mut self, expr: Expr, negated: bool) -> Result<Expr, ParseError> {
1085        self.expect(&Token::LParen)?;
1086        // Detect subquery: `( Ident ...` where the Ident is a table name.
1087        if let Token::Ident(_) = self.peek() {
1088            // Peek further: if the next token after the Ident is NOT `,` or
1089            // `)`, it's a subquery source name.
1090            let after = self.tokens.get(self.pos + 1);
1091            let is_subquery = !matches!(after, Some(Token::Comma) | Some(Token::RParen));
1092            if is_subquery {
1093                let source = match self.advance() {
1094                    Token::Ident(name) => name,
1095                    _ => unreachable!(),
1096                };
1097                let subquery = self.parse_query_tail(source)?;
1098                self.expect(&Token::RParen)?;
1099                return Ok(Expr::InSubquery {
1100                    expr: Box::new(expr),
1101                    subquery: Box::new(subquery),
1102                    negated,
1103                });
1104            }
1105        }
1106        let mut list = Vec::new();
1107        while !matches!(self.peek(), Token::RParen | Token::Eof) {
1108            list.push(self.parse_expr()?);
1109            if *self.peek() == Token::Comma {
1110                self.advance();
1111            }
1112        }
1113        self.expect(&Token::RParen)?;
1114        Ok(Expr::InList {
1115            expr: Box::new(expr),
1116            list,
1117            negated,
1118        })
1119    }
1120
1121    /// Try to parse a `(subquery)` tail for `exists` / `not exists`.
1122    /// A subquery is detected when the next tokens are `( Ident ...` —
1123    /// bare identifiers inside parens are always table/view names in
1124    /// PowQL (column refs start with `.`). Returns `Ok(Some(query))` if
1125    /// consumed, `Ok(None)` if the shape doesn't match (so the caller
1126    /// falls back to parsing a scalar primary for the legacy
1127    /// `exists <expr>` form).
1128    fn try_parse_exists_subquery(&mut self) -> Result<Option<QueryExpr>, ParseError> {
1129        if *self.peek() != Token::LParen {
1130            return Ok(None);
1131        }
1132        // Peek one token inside the paren. Anything starting with `Ident`
1133        // is a source name — PowQL column references use `DotIdent`, so
1134        // an `exists (X ...)` with a bare `X` is unambiguously a subquery.
1135        let after_lparen = self.tokens.get(self.pos + 1);
1136        if !matches!(after_lparen, Some(Token::Ident(_))) {
1137            return Ok(None);
1138        }
1139        self.expect(&Token::LParen)?;
1140        let source = match self.advance() {
1141            Token::Ident(name) => name,
1142            _ => unreachable!(),
1143        };
1144        let subquery = self.parse_query_tail(source)?;
1145        self.expect(&Token::RParen)?;
1146        Ok(Some(subquery))
1147    }
1148
1149    /// Parse `low and high` after `between` / `not between`.
1150    /// Desugars into `expr >= low AND expr <= high` (or negated:
1151    /// `expr < low OR expr > high`).
1152    fn parse_between(&mut self, expr: Expr, negated: bool) -> Result<Expr, ParseError> {
1153        let low = self.parse_additive()?;
1154        self.expect(&Token::And)?;
1155        let high = self.parse_additive()?;
1156        if negated {
1157            // NOT BETWEEN: expr < low OR expr > high
1158            Ok(Expr::BinaryOp(
1159                Box::new(Expr::BinaryOp(
1160                    Box::new(expr.clone()),
1161                    BinOp::Lt,
1162                    Box::new(low),
1163                )),
1164                BinOp::Or,
1165                Box::new(Expr::BinaryOp(Box::new(expr), BinOp::Gt, Box::new(high))),
1166            ))
1167        } else {
1168            // BETWEEN: expr >= low AND expr <= high
1169            Ok(Expr::BinaryOp(
1170                Box::new(Expr::BinaryOp(
1171                    Box::new(expr.clone()),
1172                    BinOp::Gte,
1173                    Box::new(low),
1174                )),
1175                BinOp::And,
1176                Box::new(Expr::BinaryOp(Box::new(expr), BinOp::Lte, Box::new(high))),
1177            ))
1178        }
1179    }
1180
1181    /// Parse `group .field1, .field2 [having <expr>]`.
1182    fn parse_group_by(&mut self) -> Result<GroupByClause, ParseError> {
1183        let mut keys = Vec::new();
1184        while let Token::DotIdent(name) = self.peek() {
1185            let name = name.clone();
1186            self.advance();
1187            keys.push(name);
1188            if *self.peek() == Token::Comma {
1189                self.advance();
1190            } else {
1191                break;
1192            }
1193        }
1194        if keys.is_empty() {
1195            return Err(ParseError::Syntax {
1196                message: "expected at least one .field after group".into(),
1197            });
1198        }
1199        let having = if *self.peek() == Token::Having {
1200            self.advance();
1201            Some(self.parse_expr()?)
1202        } else {
1203            None
1204        };
1205        Ok(GroupByClause { keys, having })
1206    }
1207
1208    fn parse_additive(&mut self) -> Result<Expr, ParseError> {
1209        let mut left = self.parse_multiplicative()?;
1210        loop {
1211            let op = match self.peek() {
1212                Token::Plus => BinOp::Add,
1213                Token::Minus => BinOp::Sub,
1214                Token::Coalesce => {
1215                    self.advance();
1216                    let right = self.parse_multiplicative()?;
1217                    left = Expr::Coalesce(Box::new(left), Box::new(right));
1218                    continue;
1219                }
1220                _ => break,
1221            };
1222            self.advance();
1223            let right = self.parse_multiplicative()?;
1224            left = Expr::BinaryOp(Box::new(left), op, Box::new(right));
1225        }
1226        Ok(left)
1227    }
1228
1229    fn parse_multiplicative(&mut self) -> Result<Expr, ParseError> {
1230        let mut left = self.parse_primary()?;
1231        loop {
1232            let op = match self.peek() {
1233                Token::Star => BinOp::Mul,
1234                Token::Slash => BinOp::Div,
1235                _ => break,
1236            };
1237            self.advance();
1238            let right = self.parse_primary()?;
1239            left = Expr::BinaryOp(Box::new(left), op, Box::new(right));
1240        }
1241        Ok(left)
1242    }
1243
1244    fn parse_primary(&mut self) -> Result<Expr, ParseError> {
1245        match self.peek().clone() {
1246            Token::DotIdent(name) => {
1247                self.advance();
1248                Ok(Expr::Field(name))
1249            }
1250            Token::IntLit(v) => {
1251                self.advance();
1252                Ok(Expr::Literal(Literal::Int(v)))
1253            }
1254            Token::FloatLit(v) => {
1255                self.advance();
1256                Ok(Expr::Literal(Literal::Float(v)))
1257            }
1258            Token::StringLit(v) => {
1259                self.advance();
1260                Ok(Expr::Literal(Literal::String(v)))
1261            }
1262            Token::BoolLit(v) => {
1263                self.advance();
1264                Ok(Expr::Literal(Literal::Bool(v)))
1265            }
1266            Token::Param(name) => {
1267                self.advance();
1268                Ok(Expr::Param(name))
1269            }
1270            Token::Not => {
1271                self.advance();
1272                if *self.peek() == Token::Exists {
1273                    self.advance();
1274                    // `not exists (Q)` → ExistsSubquery{ negated: true } when
1275                    // followed by `( Ident ...` (subquery form). Otherwise
1276                    // fall back to the scalar `is not null` unary op.
1277                    if let Some(sub) = self.try_parse_exists_subquery()? {
1278                        return Ok(Expr::ExistsSubquery {
1279                            subquery: Box::new(sub),
1280                            negated: true,
1281                        });
1282                    }
1283                    let expr = self.parse_primary()?;
1284                    Ok(Expr::UnaryOp(UnaryOp::NotExists, Box::new(expr)))
1285                } else {
1286                    let expr = self.parse_primary()?;
1287                    Ok(Expr::UnaryOp(UnaryOp::Not, Box::new(expr)))
1288                }
1289            }
1290            Token::Exists => {
1291                self.advance();
1292                // `exists (Q)` → ExistsSubquery when followed by a
1293                // parenthesised query. Scalar `exists .field` still parses
1294                // as UnaryOp::Exists for backwards compatibility.
1295                if let Some(sub) = self.try_parse_exists_subquery()? {
1296                    return Ok(Expr::ExistsSubquery {
1297                        subquery: Box::new(sub),
1298                        negated: false,
1299                    });
1300                }
1301                let expr = self.parse_primary()?;
1302                Ok(Expr::UnaryOp(UnaryOp::Exists, Box::new(expr)))
1303            }
1304            Token::LParen => {
1305                self.advance();
1306                let expr = self.parse_expr()?;
1307                self.expect(&Token::RParen)?;
1308                Ok(expr)
1309            }
1310            Token::Ident(name) => {
1311                self.advance();
1312                // `alias.field` → QualifiedField. The lexer emits `t1.name` as
1313                // `Ident("t1")` + `DotIdent("name")` (see lexer.rs line 30),
1314                // so a trailing DotIdent here means a qualified reference.
1315                if let Token::DotIdent(field) = self.peek().clone() {
1316                    self.advance();
1317                    return Ok(Expr::QualifiedField {
1318                        qualifier: name,
1319                        field,
1320                    });
1321                }
1322                Ok(Expr::Field(name))
1323            }
1324            // Window-only functions: row_number(), rank(), dense_rank()
1325            Token::RowNumber | Token::Rank | Token::DenseRank => {
1326                let wfunc = match self.advance() {
1327                    Token::RowNumber => WindowFunc::RowNumber,
1328                    Token::Rank => WindowFunc::Rank,
1329                    Token::DenseRank => WindowFunc::DenseRank,
1330                    _ => {
1331                        return Err(ParseError::Syntax {
1332                            message: "unexpected window function token".into(),
1333                        })
1334                    }
1335                };
1336                self.expect(&Token::LParen)?;
1337                self.expect(&Token::RParen)?;
1338                let (partition_by, order_by) = self.parse_over_clause()?;
1339                Ok(Expr::Window {
1340                    function: wfunc,
1341                    args: vec![],
1342                    partition_by,
1343                    order_by,
1344                })
1345            }
1346            // Aggregate function calls inside expressions (projections, HAVING).
1347            // Top-level `count(User)` still routes through parse_aggregate_query
1348            // in parse_statement; this arm handles `count(.id)`, `sum(.age)`, etc.
1349            Token::Count | Token::Avg | Token::Sum | Token::Min | Token::Max => {
1350                let mut func = match self.advance() {
1351                    Token::Count => AggFunc::Count,
1352                    Token::Avg => AggFunc::Avg,
1353                    Token::Sum => AggFunc::Sum,
1354                    Token::Min => AggFunc::Min,
1355                    Token::Max => AggFunc::Max,
1356                    _ => {
1357                        return Err(ParseError::Syntax {
1358                            message: "unexpected aggregate token".into(),
1359                        })
1360                    }
1361                };
1362                self.expect(&Token::LParen)?;
1363                // count(*) — count all rows including nulls
1364                if func == AggFunc::Count && *self.peek() == Token::Star {
1365                    self.advance();
1366                    self.expect(&Token::RParen)?;
1367                    // Check for OVER — count(*) over (...)
1368                    if *self.peek() == Token::Over {
1369                        let (partition_by, order_by) = self.parse_over_clause()?;
1370                        return Ok(Expr::Window {
1371                            function: WindowFunc::Count,
1372                            args: vec![Expr::Field("*".into())],
1373                            partition_by,
1374                            order_by,
1375                        });
1376                    }
1377                    return Ok(Expr::FunctionCall(
1378                        AggFunc::Count,
1379                        Box::new(Expr::Field("*".into())),
1380                    ));
1381                }
1382                // count(distinct .field) → CountDistinct
1383                if func == AggFunc::Count && *self.peek() == Token::Distinct {
1384                    self.advance();
1385                    func = AggFunc::CountDistinct;
1386                }
1387                let inner = self.parse_expr()?;
1388                self.expect(&Token::RParen)?;
1389                // Check for OVER — e.g. sum(.salary) over (...)
1390                if *self.peek() == Token::Over {
1391                    let wfunc = match func {
1392                        AggFunc::Count => WindowFunc::Count,
1393                        AggFunc::Avg => WindowFunc::Avg,
1394                        AggFunc::Sum => WindowFunc::Sum,
1395                        AggFunc::Min => WindowFunc::Min,
1396                        AggFunc::Max => WindowFunc::Max,
1397                        _ => {
1398                            return Err(ParseError::Unsupported {
1399                                feature: "count(distinct ...) over (...) is not supported".into(),
1400                            })
1401                        }
1402                    };
1403                    let (partition_by, order_by) = self.parse_over_clause()?;
1404                    return Ok(Expr::Window {
1405                        function: wfunc,
1406                        args: vec![inner],
1407                        partition_by,
1408                        order_by,
1409                    });
1410                }
1411                Ok(Expr::FunctionCall(func, Box::new(inner)))
1412            }
1413            Token::Upper
1414            | Token::Lower
1415            | Token::Length
1416            | Token::Trim
1417            | Token::Substring
1418            | Token::Concat
1419            | Token::Abs
1420            | Token::Round
1421            | Token::Ceil
1422            | Token::Floor
1423            | Token::Sqrt
1424            | Token::Pow
1425            | Token::Now
1426            | Token::Extract
1427            | Token::DateAdd
1428            | Token::DateDiff => {
1429                let tok = self.advance();
1430                let func = token_to_scalar_fn(&tok);
1431                self.expect(&Token::LParen)?;
1432                let mut args = Vec::new();
1433                while !matches!(self.peek(), Token::RParen | Token::Eof) {
1434                    args.push(self.parse_expr()?);
1435                    if *self.peek() == Token::Comma {
1436                        self.advance();
1437                    }
1438                }
1439                self.expect(&Token::RParen)?;
1440                Ok(Expr::ScalarFunc(func, args))
1441            }
1442            Token::Cast => {
1443                self.advance();
1444                self.expect(&Token::LParen)?;
1445                let inner = self.parse_expr()?;
1446                self.expect(&Token::Comma)?;
1447                let cast_type = self.parse_cast_type()?;
1448                self.expect(&Token::RParen)?;
1449                Ok(Expr::Cast(Box::new(inner), cast_type))
1450            }
1451            Token::Case => {
1452                self.advance();
1453                let mut whens = Vec::new();
1454                while *self.peek() == Token::When {
1455                    self.advance();
1456                    let condition = self.parse_expr()?;
1457                    self.expect(&Token::Then)?;
1458                    let result = self.parse_expr()?;
1459                    whens.push((Box::new(condition), Box::new(result)));
1460                }
1461                let else_expr = if *self.peek() == Token::Else {
1462                    self.advance();
1463                    Some(Box::new(self.parse_expr()?))
1464                } else {
1465                    None
1466                };
1467                self.expect(&Token::End)?;
1468                Ok(Expr::Case { whens, else_expr })
1469            }
1470            t => Err(ParseError::Syntax {
1471                message: format!("unexpected token in expression: {}", t.display_name()),
1472            }),
1473        }
1474    }
1475
1476    /// `alter <Table> add [column] [required] <name>: <type>`
1477    /// `alter <Table> drop [column] <name>`
1478    fn parse_alter_table(&mut self) -> Result<Statement, ParseError> {
1479        self.expect(&Token::Alter)?;
1480        let table = match self.advance() {
1481            Token::Ident(name) => name,
1482            t => {
1483                return Err(ParseError::UnexpectedToken {
1484                    expected: "table name after alter".into(),
1485                    got: t.display_name(),
1486                })
1487            }
1488        };
1489        match self.peek() {
1490            Token::Add => {
1491                self.advance();
1492                // `alter <Table> add index .<column>`
1493                if *self.peek() == Token::Index {
1494                    self.advance();
1495                    let column = match self.advance() {
1496                        Token::DotIdent(n) => n,
1497                        t => {
1498                            return Err(ParseError::UnexpectedToken {
1499                                expected: ".<column> after add index".into(),
1500                                got: t.display_name(),
1501                            })
1502                        }
1503                    };
1504                    return Ok(Statement::AlterTable(AlterTableExpr {
1505                        table,
1506                        action: AlterAction::AddIndex { column },
1507                    }));
1508                }
1509                // optional `column` keyword
1510                if *self.peek() == Token::Column {
1511                    self.advance();
1512                }
1513                let required = if *self.peek() == Token::Required {
1514                    self.advance();
1515                    true
1516                } else {
1517                    false
1518                };
1519                let name = match self.advance() {
1520                    Token::Ident(n) => n,
1521                    t => {
1522                        return Err(ParseError::UnexpectedToken {
1523                            expected: "column name".into(),
1524                            got: t.display_name(),
1525                        })
1526                    }
1527                };
1528                self.expect(&Token::Colon)?;
1529                let type_name = match self.advance() {
1530                    Token::Ident(n) => n,
1531                    t => {
1532                        return Err(ParseError::UnexpectedToken {
1533                            expected: "type name".into(),
1534                            got: t.display_name(),
1535                        })
1536                    }
1537                };
1538                Ok(Statement::AlterTable(AlterTableExpr {
1539                    table,
1540                    action: AlterAction::AddColumn {
1541                        name,
1542                        type_name,
1543                        required,
1544                    },
1545                }))
1546            }
1547            Token::Drop => {
1548                self.advance();
1549                // optional `column` keyword
1550                if *self.peek() == Token::Column {
1551                    self.advance();
1552                }
1553                let name = match self.advance() {
1554                    Token::Ident(n) => n,
1555                    t => {
1556                        return Err(ParseError::UnexpectedToken {
1557                            expected: "column name".into(),
1558                            got: t.display_name(),
1559                        })
1560                    }
1561                };
1562                Ok(Statement::AlterTable(AlterTableExpr {
1563                    table,
1564                    action: AlterAction::DropColumn { name },
1565                }))
1566            }
1567            t => Err(ParseError::UnexpectedToken {
1568                expected: "add or drop after alter <table>".into(),
1569                got: t.display_name(),
1570            }),
1571        }
1572    }
1573
1574    /// `drop <Table>` or `drop view <ViewName>`
1575    fn parse_drop_or_drop_view(&mut self) -> Result<Statement, ParseError> {
1576        self.expect(&Token::Drop)?;
1577        if *self.peek() == Token::View {
1578            self.advance(); // consume `view`
1579            let name = match self.advance() {
1580                Token::Ident(name) => name,
1581                t => {
1582                    return Err(ParseError::UnexpectedToken {
1583                        expected: "view name after drop view".into(),
1584                        got: t.display_name(),
1585                    })
1586                }
1587            };
1588            return Ok(Statement::DropView(DropViewExpr { name }));
1589        }
1590        let table = match self.advance() {
1591            Token::Ident(name) => name,
1592            t => {
1593                return Err(ParseError::UnexpectedToken {
1594                    expected: "table name after drop".into(),
1595                    got: t.display_name(),
1596                })
1597            }
1598        };
1599        Ok(Statement::DropTable(DropTableExpr { table }))
1600    }
1601
1602    /// `materialize <ViewName> as <Query>`
1603    ///
1604    /// The source query text is captured by slicing the original token stream
1605    /// from the position after `as` to the end.
1606    fn parse_create_view(&mut self) -> Result<Statement, ParseError> {
1607        self.expect(&Token::Materialized)?;
1608        let name = match self.advance() {
1609            Token::Ident(name) => name,
1610            t => {
1611                return Err(ParseError::UnexpectedToken {
1612                    expected: "view name after materialize".into(),
1613                    got: t.display_name(),
1614                })
1615            }
1616        };
1617        self.expect(&Token::As)?;
1618        // Record position so we can reconstruct the query text for storage.
1619        let query_start = self.pos;
1620        let source = match self.advance() {
1621            Token::Ident(s) => s,
1622            t => {
1623                return Err(ParseError::UnexpectedToken {
1624                    expected: "source table name".into(),
1625                    got: t.display_name(),
1626                })
1627            }
1628        };
1629        let query = self.parse_query_tail(source)?;
1630        // Reconstruct query text from tokens for storage and re-execution.
1631        let query_text = tokens_to_text(&self.tokens[query_start..self.pos]);
1632        Ok(Statement::CreateView(CreateViewExpr {
1633            name,
1634            query,
1635            query_text,
1636        }))
1637    }
1638
1639    /// Check for `union [all]` after a query and build a left-associative
1640    /// chain if present.
1641    fn maybe_parse_union(&mut self, left: Statement) -> Result<Statement, ParseError> {
1642        if *self.peek() != Token::Union {
1643            return Ok(left);
1644        }
1645        if !matches!(left, Statement::Query(_) | Statement::Union(_)) {
1646            return Err(ParseError::Syntax {
1647                message: "UNION requires a query on the left side".into(),
1648            });
1649        }
1650        self.advance(); // consume `union`
1651        let all = if let Token::Ident(s) = self.peek() {
1652            if s == "all" {
1653                self.advance();
1654                true
1655            } else {
1656                false
1657            }
1658        } else {
1659            false
1660        };
1661        // Parse the RHS as a single query (not chained — we'll chain ourselves).
1662        let right = self.parse_single_query()?;
1663        let union = Statement::Union(UnionExpr {
1664            left: Box::new(left),
1665            right: Box::new(right),
1666            all,
1667        });
1668        // Recursively check for further chaining: `A union B union C`
1669        self.maybe_parse_union(union)
1670    }
1671
1672    /// Parse a single query statement (no UNION chaining). Used for UNION RHS.
1673    fn parse_single_query(&mut self) -> Result<Statement, ParseError> {
1674        match self.peek() {
1675            Token::Count | Token::Avg | Token::Sum | Token::Min | Token::Max => {
1676                self.parse_aggregate_query()
1677            }
1678            Token::Ident(_) => self.parse_query_or_mutation(),
1679            _ => Err(ParseError::Syntax {
1680                message: format!(
1681                    "expected query after UNION, got {}",
1682                    self.peek().display_name()
1683                ),
1684            }),
1685        }
1686    }
1687
1688    /// `refresh <ViewName>`
1689    fn parse_refresh_view(&mut self) -> Result<Statement, ParseError> {
1690        self.expect(&Token::Refresh)?;
1691        let name = match self.advance() {
1692            Token::Ident(name) => name,
1693            t => {
1694                return Err(ParseError::UnexpectedToken {
1695                    expected: "view name after refresh".into(),
1696                    got: t.display_name(),
1697                })
1698            }
1699        };
1700        Ok(Statement::RefreshView(RefreshViewExpr { name }))
1701    }
1702
1703    fn parse_create_type(&mut self) -> Result<Statement, ParseError> {
1704        self.expect(&Token::Type)?;
1705        let name = match self.advance() {
1706            Token::Ident(n) => n,
1707            t => {
1708                return Err(ParseError::UnexpectedToken {
1709                    expected: "type name".into(),
1710                    got: t.display_name(),
1711                })
1712            }
1713        };
1714        self.expect(&Token::LBrace)?;
1715        let mut fields = Vec::new();
1716        while !matches!(self.peek(), Token::RBrace | Token::Eof) {
1717            let required = if *self.peek() == Token::Required {
1718                self.advance();
1719                true
1720            } else {
1721                false
1722            };
1723            let field_name = match self.advance() {
1724                Token::Ident(n) => n,
1725                t => {
1726                    return Err(ParseError::UnexpectedToken {
1727                        expected: "field name".into(),
1728                        got: t.display_name(),
1729                    })
1730                }
1731            };
1732            self.expect(&Token::Colon)?;
1733            let type_name = match self.advance() {
1734                Token::Ident(n) => n,
1735                t => {
1736                    return Err(ParseError::UnexpectedToken {
1737                        expected: "type name".into(),
1738                        got: t.display_name(),
1739                    })
1740                }
1741            };
1742            fields.push(FieldDef {
1743                name: field_name,
1744                type_name,
1745                required,
1746            });
1747            if *self.peek() == Token::Comma {
1748                self.advance();
1749            }
1750        }
1751        self.expect(&Token::RBrace)?;
1752        Ok(Statement::CreateType(CreateTypeExpr { name, fields }))
1753    }
1754}
1755
1756/// Reconstruct PowQL source text from a slice of tokens. Used to store the
1757/// view's source query for re-execution on refresh. Not perfectly
1758/// round-trippable (whitespace is normalised) but semantically identical.
1759fn tokens_to_text(tokens: &[Token]) -> String {
1760    let mut out = String::with_capacity(64);
1761    for tok in tokens {
1762        if !out.is_empty() && !matches!(tok, Token::Eof) {
1763            out.push(' ');
1764        }
1765        match tok {
1766            Token::Ident(s) => out.push_str(s),
1767            Token::DotIdent(s) => {
1768                out.push('.');
1769                out.push_str(s);
1770            }
1771            Token::IntLit(v) => out.push_str(&v.to_string()),
1772            Token::FloatLit(v) => out.push_str(&v.to_string()),
1773            Token::StringLit(s) => {
1774                out.push('"');
1775                out.push_str(s);
1776                out.push('"');
1777            }
1778            Token::BoolLit(v) => out.push_str(if *v { "true" } else { "false" }),
1779            Token::Param(s) => {
1780                out.push('$');
1781                out.push_str(s);
1782            }
1783            Token::Type => out.push_str("type"),
1784            Token::Filter => out.push_str("filter"),
1785            Token::Order => out.push_str("order"),
1786            Token::Limit => out.push_str("limit"),
1787            Token::Offset => out.push_str("offset"),
1788            Token::Insert => out.push_str("insert"),
1789            Token::Update => out.push_str("update"),
1790            Token::Delete => out.push_str("delete"),
1791            Token::Upsert => out.push_str("upsert"),
1792            Token::Conflict => out.push_str("conflict"),
1793            Token::Select => out.push_str("select"),
1794            Token::Required => out.push_str("required"),
1795            Token::Multi => out.push_str("multi"),
1796            Token::Link => out.push_str("link"),
1797            Token::Index => out.push_str("index"),
1798            Token::On => out.push_str("on"),
1799            Token::Asc => out.push_str("asc"),
1800            Token::Desc => out.push_str("desc"),
1801            Token::And => out.push_str("and"),
1802            Token::Or => out.push_str("or"),
1803            Token::Not => out.push_str("not"),
1804            Token::Exists => out.push_str("exists"),
1805            Token::Let => out.push_str("let"),
1806            Token::As => out.push_str("as"),
1807            Token::Match => out.push_str("match"),
1808            Token::Group => out.push_str("group"),
1809            Token::Join => out.push_str("join"),
1810            Token::Inner => out.push_str("inner"),
1811            Token::LeftKw => out.push_str("left"),
1812            Token::RightKw => out.push_str("right"),
1813            Token::Outer => out.push_str("outer"),
1814            Token::Cross => out.push_str("cross"),
1815            Token::Transaction => out.push_str("transaction"),
1816            Token::View => out.push_str("view"),
1817            Token::Materialized => out.push_str("materialized"),
1818            Token::Refresh => out.push_str("refresh"),
1819            Token::Union => out.push_str("union"),
1820            Token::Having => out.push_str("having"),
1821            Token::Distinct => out.push_str("distinct"),
1822            Token::In => out.push_str("in"),
1823            Token::Between => out.push_str("between"),
1824            Token::Like => out.push_str("like"),
1825            Token::Count => out.push_str("count"),
1826            Token::Avg => out.push_str("avg"),
1827            Token::Sum => out.push_str("sum"),
1828            Token::Min => out.push_str("min"),
1829            Token::Max => out.push_str("max"),
1830            Token::Is => out.push_str("is"),
1831            Token::Null => out.push_str("null"),
1832            Token::Upper => out.push_str("upper"),
1833            Token::Lower => out.push_str("lower"),
1834            Token::Length => out.push_str("length"),
1835            Token::Trim => out.push_str("trim"),
1836            Token::Substring => out.push_str("substring"),
1837            Token::Concat => out.push_str("concat"),
1838            Token::Abs => out.push_str("abs"),
1839            Token::Round => out.push_str("round"),
1840            Token::Ceil => out.push_str("ceil"),
1841            Token::Floor => out.push_str("floor"),
1842            Token::Sqrt => out.push_str("sqrt"),
1843            Token::Pow => out.push_str("pow"),
1844            Token::Now => out.push_str("now"),
1845            Token::Extract => out.push_str("extract"),
1846            Token::DateAdd => out.push_str("date_add"),
1847            Token::DateDiff => out.push_str("date_diff"),
1848            Token::Cast => out.push_str("cast"),
1849            Token::Case => out.push_str("case"),
1850            Token::When => out.push_str("when"),
1851            Token::Then => out.push_str("then"),
1852            Token::Else => out.push_str("else"),
1853            Token::End => out.push_str("end"),
1854            Token::Over => out.push_str("over"),
1855            Token::Partition => out.push_str("partition"),
1856            Token::RowNumber => out.push_str("row_number"),
1857            Token::Rank => out.push_str("rank"),
1858            Token::DenseRank => out.push_str("dense_rank"),
1859            Token::Alter => out.push_str("alter"),
1860            Token::Drop => out.push_str("drop"),
1861            Token::Add => out.push_str("add"),
1862            Token::Column => out.push_str("column"),
1863            Token::Eq => out.push('='),
1864            Token::Neq => out.push_str("!="),
1865            Token::Lt => out.push('<'),
1866            Token::Gt => out.push('>'),
1867            Token::Lte => out.push_str("<="),
1868            Token::Gte => out.push_str(">="),
1869            Token::Assign => out.push_str(":="),
1870            Token::Arrow => out.push_str("->"),
1871            Token::Pipe => out.push('|'),
1872            Token::Coalesce => out.push_str("??"),
1873            Token::Plus => out.push('+'),
1874            Token::Minus => out.push('-'),
1875            Token::Star => out.push('*'),
1876            Token::Slash => out.push('/'),
1877            Token::LBrace => out.push('{'),
1878            Token::RBrace => out.push('}'),
1879            Token::LParen => out.push('('),
1880            Token::RParen => out.push(')'),
1881            Token::Comma => out.push(','),
1882            Token::Colon => out.push(':'),
1883            Token::Dot => out.push('.'),
1884            Token::Explain => out.push_str("explain"),
1885            Token::Eof => {}
1886        }
1887    }
1888    out
1889}
1890
1891#[cfg(test)]
1892mod tests {
1893    use super::*;
1894    #[test]
1895    fn test_parse_simple_query() {
1896        let stmt = parse("User").unwrap();
1897        match stmt {
1898            Statement::Query(q) => {
1899                assert_eq!(q.source, "User");
1900                assert!(q.filter.is_none());
1901                assert!(q.projection.is_none());
1902            }
1903            _ => panic!("expected query"),
1904        }
1905    }
1906
1907    #[test]
1908    fn test_parse_filter() {
1909        let stmt = parse("User filter .age > 30").unwrap();
1910        match stmt {
1911            Statement::Query(q) => {
1912                assert_eq!(q.source, "User");
1913                assert!(q.filter.is_some());
1914            }
1915            _ => panic!("expected query"),
1916        }
1917    }
1918
1919    #[test]
1920    fn test_parse_projection() {
1921        let stmt = parse("User { name, email }").unwrap();
1922        match stmt {
1923            Statement::Query(q) => {
1924                let proj = q.projection.unwrap();
1925                assert_eq!(proj.len(), 2);
1926            }
1927            _ => panic!("expected query"),
1928        }
1929    }
1930
1931    #[test]
1932    fn test_parse_filter_order_limit() {
1933        let stmt = parse("User filter .age > 30 order .name desc limit 10").unwrap();
1934        match stmt {
1935            Statement::Query(q) => {
1936                assert!(q.filter.is_some());
1937                let order = q.order.unwrap();
1938                assert_eq!(order.keys.len(), 1);
1939                assert_eq!(order.keys[0].field, "name");
1940                assert!(order.keys[0].descending);
1941                assert!(q.limit.is_some());
1942            }
1943            _ => panic!("expected query"),
1944        }
1945    }
1946
1947    #[test]
1948    fn test_parse_insert() {
1949        let stmt = parse(r#"insert User { name := "Alice", age := 30 }"#).unwrap();
1950        match stmt {
1951            Statement::Insert(ins) => {
1952                assert_eq!(ins.target, "User");
1953                assert_eq!(ins.assignments.len(), 2);
1954                assert_eq!(ins.assignments[0].field, "name");
1955                assert_eq!(ins.assignments[1].field, "age");
1956            }
1957            _ => panic!("expected insert"),
1958        }
1959    }
1960
1961    #[test]
1962    fn test_parse_update() {
1963        let stmt = parse(r#"User filter .email = "alice@ex.com" update { age := 31 }"#).unwrap();
1964        match stmt {
1965            Statement::UpdateQuery(upd) => {
1966                assert_eq!(upd.source, "User");
1967                assert!(upd.filter.is_some());
1968                assert_eq!(upd.assignments.len(), 1);
1969            }
1970            _ => panic!("expected update"),
1971        }
1972    }
1973
1974    #[test]
1975    fn test_parse_delete() {
1976        let stmt = parse("User filter .age < 18 delete").unwrap();
1977        match stmt {
1978            Statement::DeleteQuery(del) => {
1979                assert_eq!(del.source, "User");
1980                assert!(del.filter.is_some());
1981            }
1982            _ => panic!("expected delete"),
1983        }
1984    }
1985
1986    #[test]
1987    fn test_parse_count() {
1988        let stmt = parse("count(User)").unwrap();
1989        match stmt {
1990            Statement::Query(q) => {
1991                let agg = q.aggregation.unwrap();
1992                assert_eq!(agg.function, AggFunc::Count);
1993                assert!(q.filter.is_none());
1994            }
1995            _ => panic!("expected query with aggregation"),
1996        }
1997    }
1998
1999    #[test]
2000    fn test_parse_count_with_filter() {
2001        // Regression: previously returned "expected RParen, got Filter".
2002        // count(<query>) must accept a full read-pipeline tail.
2003        let stmt = parse("count(User filter .age > 30)").unwrap();
2004        match stmt {
2005            Statement::Query(q) => {
2006                assert_eq!(q.source, "User");
2007                let agg = q.aggregation.unwrap();
2008                assert_eq!(agg.function, AggFunc::Count);
2009                assert!(q.filter.is_some(), "filter should have been parsed");
2010            }
2011            _ => panic!("expected query with aggregation"),
2012        }
2013    }
2014
2015    #[test]
2016    fn test_parse_count_with_filter_and_limit() {
2017        let stmt = parse("count(User filter .age > 30 limit 100)").unwrap();
2018        match stmt {
2019            Statement::Query(q) => {
2020                assert_eq!(q.source, "User");
2021                assert!(q.filter.is_some());
2022                assert!(q.limit.is_some());
2023                assert_eq!(q.aggregation.unwrap().function, AggFunc::Count);
2024            }
2025            _ => panic!("expected query with aggregation"),
2026        }
2027    }
2028
2029    #[test]
2030    fn test_parse_create_type() {
2031        let stmt = parse("type User { required name: str, age: int }").unwrap();
2032        match stmt {
2033            Statement::CreateType(ct) => {
2034                assert_eq!(ct.name, "User");
2035                assert_eq!(ct.fields.len(), 2);
2036                assert!(ct.fields[0].required);
2037                assert!(!ct.fields[1].required);
2038            }
2039            _ => panic!("expected create type"),
2040        }
2041    }
2042
2043    #[test]
2044    fn test_parse_sum_with_field_projection() {
2045        // `sum(... { .age })` should lift `.age` into AggregateExpr.field and
2046        // clear the projection so the executor's aggregate fast path fires.
2047        let stmt = parse("sum(User filter .age > 30 { .age })").unwrap();
2048        match stmt {
2049            Statement::Query(q) => {
2050                let agg = q.aggregation.expect("aggregate");
2051                assert_eq!(agg.function, AggFunc::Sum);
2052                assert_eq!(agg.field.as_deref(), Some("age"));
2053                assert!(
2054                    q.projection.is_none(),
2055                    "projection should be lifted into agg.field"
2056                );
2057            }
2058            _ => panic!("expected query"),
2059        }
2060    }
2061
2062    #[test]
2063    fn test_parse_avg_min_max_with_field() {
2064        for (src, expected) in [
2065            ("avg(User { .age })", AggFunc::Avg),
2066            ("min(User { .age })", AggFunc::Min),
2067            ("max(User { .age })", AggFunc::Max),
2068        ] {
2069            let stmt = parse(src).unwrap();
2070            match stmt {
2071                Statement::Query(q) => {
2072                    let agg = q.aggregation.unwrap();
2073                    assert_eq!(agg.function, expected, "func mismatch for {src}");
2074                    assert_eq!(
2075                        agg.field.as_deref(),
2076                        Some("age"),
2077                        "field mismatch for {src}"
2078                    );
2079                    assert!(
2080                        q.projection.is_none(),
2081                        "projection should be cleared for {src}"
2082                    );
2083                }
2084                _ => panic!("expected query for {src}"),
2085            }
2086        }
2087    }
2088
2089    #[test]
2090    fn test_parse_count_leaves_projection_alone() {
2091        // count() doesn't need a target field, so the projection (if any)
2092        // stays intact. It's silly to project inside a count, but it's legal.
2093        let stmt = parse("count(User { .age })").unwrap();
2094        match stmt {
2095            Statement::Query(q) => {
2096                let agg = q.aggregation.unwrap();
2097                assert_eq!(agg.function, AggFunc::Count);
2098                assert!(agg.field.is_none());
2099                assert!(q.projection.is_some(), "count must not eat projection");
2100            }
2101            _ => panic!("expected query"),
2102        }
2103    }
2104
2105    // ---- Mission E1.1: JOIN parser tests ----------------------------------
2106    // Parser-level only. The planner rejects joins with a clean error until
2107    // E1.2 wires up execution.
2108
2109    #[test]
2110    fn test_parse_source_alias() {
2111        let stmt = parse("User as u filter u.age > 30").unwrap();
2112        match stmt {
2113            Statement::Query(q) => {
2114                assert_eq!(q.source, "User");
2115                assert_eq!(q.alias.as_deref(), Some("u"));
2116                assert!(q.joins.is_empty());
2117                match q.filter.unwrap() {
2118                    Expr::BinaryOp(l, BinOp::Gt, _) => match *l {
2119                        Expr::QualifiedField { qualifier, field } => {
2120                            assert_eq!(qualifier, "u");
2121                            assert_eq!(field, "age");
2122                        }
2123                        other => panic!("expected qualified field, got {other:?}"),
2124                    },
2125                    other => panic!("expected >, got {other:?}"),
2126                }
2127            }
2128            _ => panic!("expected query"),
2129        }
2130    }
2131
2132    #[test]
2133    fn test_parse_inner_join_on() {
2134        let stmt = parse("User as u inner join Order as o on u.id = o.user_id").unwrap();
2135        match stmt {
2136            Statement::Query(q) => {
2137                assert_eq!(q.source, "User");
2138                assert_eq!(q.alias.as_deref(), Some("u"));
2139                assert_eq!(q.joins.len(), 1);
2140                let j = &q.joins[0];
2141                assert_eq!(j.kind, JoinKind::Inner);
2142                assert_eq!(j.source, "Order");
2143                assert_eq!(j.alias.as_deref(), Some("o"));
2144                let on = j.on.as_ref().expect("on clause");
2145                match on {
2146                    Expr::BinaryOp(l, BinOp::Eq, r) => {
2147                        assert!(matches!(**l, Expr::QualifiedField { .. }));
2148                        assert!(matches!(**r, Expr::QualifiedField { .. }));
2149                    }
2150                    other => panic!("expected eq, got {other:?}"),
2151                }
2152            }
2153            _ => panic!("expected query"),
2154        }
2155    }
2156
2157    #[test]
2158    fn test_parse_bare_join_defaults_to_inner() {
2159        let stmt = parse("User join Order on User.id = Order.user_id").unwrap();
2160        match stmt {
2161            Statement::Query(q) => {
2162                assert_eq!(q.joins.len(), 1);
2163                assert_eq!(q.joins[0].kind, JoinKind::Inner);
2164            }
2165            _ => panic!("expected query"),
2166        }
2167    }
2168
2169    #[test]
2170    fn test_parse_left_outer_join() {
2171        let stmt = parse("User as u left outer join Order as o on u.id = o.user_id").unwrap();
2172        match stmt {
2173            Statement::Query(q) => {
2174                assert_eq!(q.joins.len(), 1);
2175                assert_eq!(q.joins[0].kind, JoinKind::LeftOuter);
2176            }
2177            _ => panic!("expected query"),
2178        }
2179    }
2180
2181    #[test]
2182    fn test_parse_left_join_without_outer_keyword() {
2183        // `left join` is shorthand for `left outer join` in SQL — we accept it.
2184        let stmt = parse("User as u left join Order as o on u.id = o.user_id").unwrap();
2185        match stmt {
2186            Statement::Query(q) => {
2187                assert_eq!(q.joins[0].kind, JoinKind::LeftOuter);
2188            }
2189            _ => panic!("expected query"),
2190        }
2191    }
2192
2193    #[test]
2194    fn test_parse_right_join() {
2195        let stmt = parse("User as u right join Order as o on u.id = o.user_id").unwrap();
2196        match stmt {
2197            Statement::Query(q) => {
2198                assert_eq!(q.joins[0].kind, JoinKind::RightOuter);
2199            }
2200            _ => panic!("expected query"),
2201        }
2202    }
2203
2204    #[test]
2205    fn test_parse_cross_join_has_no_on() {
2206        let stmt = parse("User cross join Order").unwrap();
2207        match stmt {
2208            Statement::Query(q) => {
2209                assert_eq!(q.joins[0].kind, JoinKind::Cross);
2210                assert!(q.joins[0].on.is_none());
2211            }
2212            _ => panic!("expected query"),
2213        }
2214    }
2215
2216    #[test]
2217    fn test_parse_multi_join_chain() {
2218        let stmt = parse(
2219            "User as u join Order as o on u.id = o.user_id \
2220             join Product as p on o.product_id = p.id",
2221        )
2222        .unwrap();
2223        match stmt {
2224            Statement::Query(q) => {
2225                assert_eq!(q.joins.len(), 2);
2226                assert_eq!(q.joins[0].source, "Order");
2227                assert_eq!(q.joins[1].source, "Product");
2228            }
2229            _ => panic!("expected query"),
2230        }
2231    }
2232
2233    #[test]
2234    fn test_parse_join_with_filter_tail() {
2235        // Filter/order/limit still work after a join clause.
2236        let stmt = parse(
2237            "User as u join Order as o on u.id = o.user_id \
2238             filter o.total > 100 order .name limit 10",
2239        )
2240        .unwrap();
2241        match stmt {
2242            Statement::Query(q) => {
2243                assert_eq!(q.joins.len(), 1);
2244                assert!(q.filter.is_some());
2245                assert!(q.order.is_some());
2246                assert!(q.limit.is_some());
2247            }
2248            _ => panic!("expected query"),
2249        }
2250    }
2251
2252    #[test]
2253    fn test_parse_join_requires_on_for_inner() {
2254        // Non-cross joins require `on <expr>`. Missing `on` is a parse error.
2255        let err = parse("User join Order").unwrap_err();
2256        assert!(
2257            err.message().contains("on"),
2258            "expected on-clause error, got {:?}",
2259            err.message()
2260        );
2261    }
2262
2263    #[test]
2264    fn test_parse_update_on_joined_query_errors() {
2265        // E1.1 explicitly rejects update/delete on joined queries — SQL
2266        // semantics here are messy and we're not implementing them yet.
2267        let err =
2268            parse("User as u join Order as o on u.id = o.user_id update { age := 1 }").unwrap_err();
2269        assert!(err.message().contains("update"));
2270    }
2271
2272    #[test]
2273    fn test_parse_delete_on_joined_query_errors() {
2274        let err = parse("User as u join Order as o on u.id = o.user_id delete").unwrap_err();
2275        assert!(err.message().contains("delete"));
2276    }
2277
2278    // ---- Mission E2a: DISTINCT + IN-list + BETWEEN + LIKE -----------------
2279
2280    #[test]
2281    fn test_parse_distinct() {
2282        let stmt = parse("User distinct { .name }").unwrap();
2283        match stmt {
2284            Statement::Query(q) => {
2285                assert!(q.distinct);
2286                assert!(q.projection.is_some());
2287            }
2288            _ => panic!("expected query"),
2289        }
2290    }
2291
2292    #[test]
2293    fn test_parse_in_list() {
2294        let stmt = parse(r#"User filter .name in ("Alice", "Bob")"#).unwrap();
2295        match stmt {
2296            Statement::Query(q) => match q.filter.unwrap() {
2297                Expr::InList {
2298                    expr,
2299                    list,
2300                    negated,
2301                } => {
2302                    assert!(!negated);
2303                    assert!(matches!(*expr, Expr::Field(f) if f == "name"));
2304                    assert_eq!(list.len(), 2);
2305                }
2306                other => panic!("expected InList, got {other:?}"),
2307            },
2308            _ => panic!("expected query"),
2309        }
2310    }
2311
2312    #[test]
2313    fn test_parse_not_in_list() {
2314        let stmt = parse("User filter .age not in (1, 2, 3)").unwrap();
2315        match stmt {
2316            Statement::Query(q) => match q.filter.unwrap() {
2317                Expr::InList { negated, list, .. } => {
2318                    assert!(negated);
2319                    assert_eq!(list.len(), 3);
2320                }
2321                other => panic!("expected InList, got {other:?}"),
2322            },
2323            _ => panic!("expected query"),
2324        }
2325    }
2326
2327    #[test]
2328    fn test_parse_between() {
2329        // BETWEEN desugars into >= AND <=.
2330        let stmt = parse("User filter .age between 10 and 20").unwrap();
2331        match stmt {
2332            Statement::Query(q) => {
2333                match q.filter.unwrap() {
2334                    Expr::BinaryOp(_, BinOp::And, _) => {} // desugared
2335                    other => panic!("expected And (desugared between), got {other:?}"),
2336                }
2337            }
2338            _ => panic!("expected query"),
2339        }
2340    }
2341
2342    #[test]
2343    fn test_parse_not_between() {
2344        // NOT BETWEEN desugars into < OR >.
2345        let stmt = parse("User filter .age not between 10 and 20").unwrap();
2346        match stmt {
2347            Statement::Query(q) => {
2348                match q.filter.unwrap() {
2349                    Expr::BinaryOp(_, BinOp::Or, _) => {} // desugared
2350                    other => panic!("expected Or (desugared not between), got {other:?}"),
2351                }
2352            }
2353            _ => panic!("expected query"),
2354        }
2355    }
2356
2357    #[test]
2358    fn test_parse_like() {
2359        let stmt = parse(r#"User filter .name like "A%""#).unwrap();
2360        match stmt {
2361            Statement::Query(q) => match q.filter.unwrap() {
2362                Expr::BinaryOp(l, BinOp::Like, r) => {
2363                    assert!(matches!(*l, Expr::Field(f) if f == "name"));
2364                    assert!(matches!(*r, Expr::Literal(Literal::String(s)) if s == "A%"));
2365                }
2366                other => panic!("expected Like, got {other:?}"),
2367            },
2368            _ => panic!("expected query"),
2369        }
2370    }
2371
2372    #[test]
2373    fn test_parse_not_like() {
2374        let stmt = parse(r#"User filter .name not like "A%""#).unwrap();
2375        match stmt {
2376            Statement::Query(q) => match q.filter.unwrap() {
2377                Expr::UnaryOp(UnaryOp::Not, inner) => {
2378                    assert!(matches!(*inner, Expr::BinaryOp(_, BinOp::Like, _)));
2379                }
2380                other => panic!("expected Not(Like), got {other:?}"),
2381            },
2382            _ => panic!("expected query"),
2383        }
2384    }
2385
2386    // ---- Mission E2b: GROUP BY + HAVING ------------------------------------
2387
2388    #[test]
2389    fn test_parse_group_by_single_key() {
2390        let stmt = parse("User group .status { .status, n: count(.name) }").unwrap();
2391        match stmt {
2392            Statement::Query(q) => {
2393                let gb = q.group_by.unwrap();
2394                assert_eq!(gb.keys, vec!["status"]);
2395                assert!(gb.having.is_none());
2396                let proj = q.projection.unwrap();
2397                assert_eq!(proj.len(), 2);
2398                assert!(matches!(
2399                    &proj[1].expr,
2400                    Expr::FunctionCall(AggFunc::Count, _)
2401                ));
2402                assert_eq!(proj[1].alias.as_deref(), Some("n"));
2403            }
2404            _ => panic!("expected query"),
2405        }
2406    }
2407
2408    #[test]
2409    fn test_parse_group_by_multi_key() {
2410        let stmt = parse("User group .status, .age { .status, .age }").unwrap();
2411        match stmt {
2412            Statement::Query(q) => {
2413                let gb = q.group_by.unwrap();
2414                assert_eq!(gb.keys, vec!["status", "age"]);
2415            }
2416            _ => panic!("expected query"),
2417        }
2418    }
2419
2420    #[test]
2421    fn test_parse_group_by_having() {
2422        let stmt = parse("User group .status having count(.name) > 1 { .status }").unwrap();
2423        match stmt {
2424            Statement::Query(q) => {
2425                let gb = q.group_by.unwrap();
2426                assert_eq!(gb.keys, vec!["status"]);
2427                assert!(gb.having.is_some());
2428                // HAVING is `count(.name) > 1` — BinaryOp(FunctionCall, Gt, Literal)
2429                match gb.having.unwrap() {
2430                    Expr::BinaryOp(l, BinOp::Gt, _) => {
2431                        assert!(matches!(*l, Expr::FunctionCall(AggFunc::Count, _)));
2432                    }
2433                    other => panic!("expected BinaryOp, got {other:?}"),
2434                }
2435            }
2436            _ => panic!("expected query"),
2437        }
2438    }
2439
2440    #[test]
2441    fn test_parse_aggregate_in_projection() {
2442        // Unaliased aggregate function calls in projection.
2443        let stmt = parse("User group .status { .status, count(.name), sum(.age) }").unwrap();
2444        match stmt {
2445            Statement::Query(q) => {
2446                let proj = q.projection.unwrap();
2447                assert_eq!(proj.len(), 3);
2448                assert!(matches!(
2449                    &proj[1].expr,
2450                    Expr::FunctionCall(AggFunc::Count, _)
2451                ));
2452                assert!(matches!(&proj[2].expr, Expr::FunctionCall(AggFunc::Sum, _)));
2453            }
2454            _ => panic!("expected query"),
2455        }
2456    }
2457
2458    #[test]
2459    fn test_parse_aggregate_in_aliased_projection() {
2460        let stmt = parse("User group .status { .status, total: count(.name), average: avg(.age) }")
2461            .unwrap();
2462        match stmt {
2463            Statement::Query(q) => {
2464                let proj = q.projection.unwrap();
2465                assert_eq!(proj[1].alias.as_deref(), Some("total"));
2466                assert!(matches!(
2467                    &proj[1].expr,
2468                    Expr::FunctionCall(AggFunc::Count, _)
2469                ));
2470                assert_eq!(proj[2].alias.as_deref(), Some("average"));
2471                assert!(matches!(&proj[2].expr, Expr::FunctionCall(AggFunc::Avg, _)));
2472            }
2473            _ => panic!("expected query"),
2474        }
2475    }
2476
2477    // ─── IS NULL / IS NOT NULL parser tests ────────────────────────────
2478
2479    #[test]
2480    fn test_parse_is_null() {
2481        let stmt = parse("User filter .age is null").unwrap();
2482        match stmt {
2483            Statement::Query(q) => {
2484                let filter = q.filter.unwrap();
2485                assert_eq!(
2486                    filter,
2487                    Expr::UnaryOp(UnaryOp::IsNull, Box::new(Expr::Field("age".into())))
2488                );
2489            }
2490            _ => panic!("expected query"),
2491        }
2492    }
2493
2494    #[test]
2495    fn test_parse_is_not_null() {
2496        let stmt = parse("User filter .age is not null").unwrap();
2497        match stmt {
2498            Statement::Query(q) => {
2499                let filter = q.filter.unwrap();
2500                assert_eq!(
2501                    filter,
2502                    Expr::UnaryOp(UnaryOp::IsNotNull, Box::new(Expr::Field("age".into())))
2503                );
2504            }
2505            _ => panic!("expected query"),
2506        }
2507    }
2508
2509    #[test]
2510    fn test_parse_eq_null_desugars_to_is_null() {
2511        let stmt = parse("User filter .age = null").unwrap();
2512        match stmt {
2513            Statement::Query(q) => {
2514                let filter = q.filter.unwrap();
2515                assert_eq!(
2516                    filter,
2517                    Expr::UnaryOp(UnaryOp::IsNull, Box::new(Expr::Field("age".into())))
2518                );
2519            }
2520            _ => panic!("expected query"),
2521        }
2522    }
2523
2524    #[test]
2525    fn test_parse_neq_null_desugars_to_is_not_null() {
2526        let stmt = parse("User filter .age != null").unwrap();
2527        match stmt {
2528            Statement::Query(q) => {
2529                let filter = q.filter.unwrap();
2530                assert_eq!(
2531                    filter,
2532                    Expr::UnaryOp(UnaryOp::IsNotNull, Box::new(Expr::Field("age".into())))
2533                );
2534            }
2535            _ => panic!("expected query"),
2536        }
2537    }
2538
2539    #[test]
2540    fn test_parse_ordering_null_still_errors() {
2541        // `< null`, `>= null` etc. are nonsensical — leave them as errors.
2542        assert!(parse("User filter .age < null").is_err());
2543        assert!(parse("User filter .age >= null").is_err());
2544    }
2545
2546    #[test]
2547    fn test_parse_count_star_expr() {
2548        let stmt = parse("User filter count(*) > 0").unwrap();
2549        match stmt {
2550            Statement::Query(q) => {
2551                let filter = q.filter.unwrap();
2552                match filter {
2553                    Expr::BinaryOp(left, BinOp::Gt, _) => {
2554                        assert_eq!(
2555                            *left,
2556                            Expr::FunctionCall(AggFunc::Count, Box::new(Expr::Field("*".into())))
2557                        );
2558                    }
2559                    _ => panic!("expected comparison"),
2560                }
2561            }
2562            _ => panic!("expected query"),
2563        }
2564    }
2565
2566    // ─── String function parser tests ──────────────────────────────────
2567
2568    #[test]
2569    fn test_parse_upper_in_filter() {
2570        let stmt = parse(r#"User filter upper(.name) = "ALICE""#).unwrap();
2571        match stmt {
2572            Statement::Query(q) => {
2573                let f = q.filter.unwrap();
2574                match f {
2575                    Expr::BinaryOp(left, BinOp::Eq, _right) => {
2576                        assert!(matches!(*left, Expr::ScalarFunc(ScalarFn::Upper, _)));
2577                    }
2578                    _ => panic!("expected binary op with upper"),
2579                }
2580            }
2581            _ => panic!("expected query"),
2582        }
2583    }
2584
2585    #[test]
2586    fn test_parse_substring() {
2587        let stmt = parse("User { sub: substring(.name, 1, 3) }").unwrap();
2588        match stmt {
2589            Statement::Query(q) => {
2590                let proj = q.projection.unwrap();
2591                match &proj[0].expr {
2592                    Expr::ScalarFunc(ScalarFn::Substring, args) => {
2593                        assert_eq!(args.len(), 3);
2594                    }
2595                    other => panic!("expected ScalarFunc Substring, got {other:?}"),
2596                }
2597            }
2598            _ => panic!("expected query"),
2599        }
2600    }
2601
2602    #[test]
2603    fn test_parse_concat() {
2604        let stmt = parse(r#"User { full: concat(.name, " - ", .email) }"#).unwrap();
2605        match stmt {
2606            Statement::Query(q) => {
2607                let proj = q.projection.unwrap();
2608                match &proj[0].expr {
2609                    Expr::ScalarFunc(ScalarFn::Concat, args) => {
2610                        assert_eq!(args.len(), 3);
2611                    }
2612                    other => panic!("expected ScalarFunc Concat, got {other:?}"),
2613                }
2614            }
2615            _ => panic!("expected query"),
2616        }
2617    }
2618
2619    // ─── CASE WHEN parser tests ────────────────────────────────────────
2620
2621    #[test]
2622    fn test_parse_case_single_when() {
2623        let stmt = parse(r#"User filter case when .age > 30 then true else false end"#).unwrap();
2624        match stmt {
2625            Statement::Query(q) => {
2626                let filter = q.filter.unwrap();
2627                match filter {
2628                    Expr::Case { whens, else_expr } => {
2629                        assert_eq!(whens.len(), 1);
2630                        assert!(else_expr.is_some());
2631                    }
2632                    other => panic!("expected Case expr, got {other:?}"),
2633                }
2634            }
2635            _ => panic!("expected query"),
2636        }
2637    }
2638
2639    #[test]
2640    fn test_parse_case_multiple_whens() {
2641        let stmt = parse(
2642            r#"User { label: case when .age > 30 then "senior" when .age > 20 then "adult" else "young" end }"#
2643        ).unwrap();
2644        match stmt {
2645            Statement::Query(q) => {
2646                let proj = q.projection.unwrap();
2647                match &proj[0].expr {
2648                    Expr::Case { whens, else_expr } => {
2649                        assert_eq!(whens.len(), 2);
2650                        assert!(else_expr.is_some());
2651                    }
2652                    other => panic!("expected Case expr, got {other:?}"),
2653                }
2654            }
2655            _ => panic!("expected query"),
2656        }
2657    }
2658
2659    #[test]
2660    fn test_parse_case_without_else() {
2661        let stmt = parse(r#"User filter case when .age > 30 then true end"#).unwrap();
2662        match stmt {
2663            Statement::Query(q) => {
2664                let filter = q.filter.unwrap();
2665                match filter {
2666                    Expr::Case { whens, else_expr } => {
2667                        assert_eq!(whens.len(), 1);
2668                        assert!(else_expr.is_none());
2669                    }
2670                    other => panic!("expected Case expr, got {other:?}"),
2671                }
2672            }
2673            _ => panic!("expected query"),
2674        }
2675    }
2676
2677    // ─── Mul/Div expression tests (E2f) ───────────────────────────────
2678
2679    #[test]
2680    fn test_parse_mul_expr() {
2681        let stmt = parse("User filter .price * .quantity > 100").unwrap();
2682        match stmt {
2683            Statement::Query(q) => {
2684                let filter = q.filter.unwrap();
2685                match filter {
2686                    Expr::BinaryOp(left, BinOp::Gt, _) => match *left {
2687                        Expr::BinaryOp(_, BinOp::Mul, _) => {}
2688                        other => panic!("expected Mul, got {other:?}"),
2689                    },
2690                    other => panic!("expected BinaryOp Gt, got {other:?}"),
2691                }
2692            }
2693            _ => panic!("expected query"),
2694        }
2695    }
2696
2697    #[test]
2698    fn test_parse_div_expr() {
2699        let stmt = parse("User { ratio: .total / .count }").unwrap();
2700        match stmt {
2701            Statement::Query(q) => {
2702                let proj = q.projection.unwrap();
2703                assert_eq!(proj[0].alias.as_deref(), Some("ratio"));
2704                match &proj[0].expr {
2705                    Expr::BinaryOp(_, BinOp::Div, _) => {}
2706                    other => panic!("expected Div, got {other:?}"),
2707                }
2708            }
2709            _ => panic!("expected query"),
2710        }
2711    }
2712
2713    #[test]
2714    fn test_parse_mul_div_precedence() {
2715        // .a + .b * .c should parse as .a + (.b * .c)
2716        let stmt = parse("User filter .a + .b * .c > 0").unwrap();
2717        match stmt {
2718            Statement::Query(q) => {
2719                let filter = q.filter.unwrap();
2720                match filter {
2721                    Expr::BinaryOp(left, BinOp::Gt, _) => match *left {
2722                        Expr::BinaryOp(_, BinOp::Add, right) => {
2723                            assert!(matches!(*right, Expr::BinaryOp(_, BinOp::Mul, _)));
2724                        }
2725                        other => panic!("expected Add, got {other:?}"),
2726                    },
2727                    other => panic!("expected Gt, got {other:?}"),
2728                }
2729            }
2730            _ => panic!("expected query"),
2731        }
2732    }
2733
2734    // ─── Multi-column ORDER BY tests (E2f) ────────────────────────────
2735
2736    #[test]
2737    fn test_parse_multi_order() {
2738        let stmt = parse("User order .name asc, .age desc").unwrap();
2739        match stmt {
2740            Statement::Query(q) => {
2741                let order = q.order.unwrap();
2742                assert_eq!(order.keys.len(), 2);
2743                assert_eq!(order.keys[0].field, "name");
2744                assert!(!order.keys[0].descending);
2745                assert_eq!(order.keys[1].field, "age");
2746                assert!(order.keys[1].descending);
2747            }
2748            _ => panic!("expected query"),
2749        }
2750    }
2751
2752    #[test]
2753    fn test_parse_order_default_asc() {
2754        let stmt = parse("User order .name").unwrap();
2755        match stmt {
2756            Statement::Query(q) => {
2757                let order = q.order.unwrap();
2758                assert_eq!(order.keys.len(), 1);
2759                assert!(!order.keys[0].descending);
2760            }
2761            _ => panic!("expected query"),
2762        }
2763    }
2764
2765    // ─── ALTER TABLE / DROP TABLE parser tests (E2g) ──────────────────
2766
2767    #[test]
2768    fn test_parse_alter_add_column() {
2769        let stmt = parse("alter User add column status: str").unwrap();
2770        match stmt {
2771            Statement::AlterTable(at) => {
2772                assert_eq!(at.table, "User");
2773                match at.action {
2774                    AlterAction::AddColumn {
2775                        name,
2776                        type_name,
2777                        required,
2778                    } => {
2779                        assert_eq!(name, "status");
2780                        assert_eq!(type_name, "str");
2781                        assert!(!required);
2782                    }
2783                    other => panic!("expected AddColumn, got {other:?}"),
2784                }
2785            }
2786            other => panic!("expected AlterTable, got {other:?}"),
2787        }
2788    }
2789
2790    #[test]
2791    fn test_parse_alter_add_required_column() {
2792        let stmt = parse("alter User add required status: str").unwrap();
2793        match stmt {
2794            Statement::AlterTable(at) => match at.action {
2795                AlterAction::AddColumn { required, .. } => assert!(required),
2796                other => panic!("expected AddColumn, got {other:?}"),
2797            },
2798            other => panic!("expected AlterTable, got {other:?}"),
2799        }
2800    }
2801
2802    #[test]
2803    fn test_parse_alter_drop_column() {
2804        let stmt = parse("alter User drop column status").unwrap();
2805        match stmt {
2806            Statement::AlterTable(at) => {
2807                assert_eq!(at.table, "User");
2808                match at.action {
2809                    AlterAction::DropColumn { name } => assert_eq!(name, "status"),
2810                    other => panic!("expected DropColumn, got {other:?}"),
2811                }
2812            }
2813            other => panic!("expected AlterTable, got {other:?}"),
2814        }
2815    }
2816
2817    #[test]
2818    fn test_parse_alter_drop_without_column_keyword() {
2819        let stmt = parse("alter User drop status").unwrap();
2820        match stmt {
2821            Statement::AlterTable(at) => match at.action {
2822                AlterAction::DropColumn { name } => assert_eq!(name, "status"),
2823                other => panic!("expected DropColumn, got {other:?}"),
2824            },
2825            other => panic!("expected AlterTable, got {other:?}"),
2826        }
2827    }
2828
2829    #[test]
2830    fn test_parse_drop_table() {
2831        let stmt = parse("drop User").unwrap();
2832        match stmt {
2833            Statement::DropTable(dt) => assert_eq!(dt.table, "User"),
2834            other => panic!("expected DropTable, got {other:?}"),
2835        }
2836    }
2837
2838    // ─── IN subquery parser tests (E2h) ───────────────────────────────
2839
2840    #[test]
2841    fn test_parse_in_subquery() {
2842        let stmt = parse("User filter .name in (VIP { .name })").unwrap();
2843        match stmt {
2844            Statement::Query(q) => {
2845                let filter = q.filter.unwrap();
2846                match filter {
2847                    Expr::InSubquery {
2848                        expr,
2849                        subquery,
2850                        negated,
2851                    } => {
2852                        assert!(!negated);
2853                        assert!(matches!(*expr, Expr::Field(ref f) if f == "name"));
2854                        assert_eq!(subquery.source, "VIP");
2855                    }
2856                    other => panic!("expected InSubquery, got {other:?}"),
2857                }
2858            }
2859            _ => panic!("expected query"),
2860        }
2861    }
2862
2863    #[test]
2864    fn test_parse_not_in_subquery() {
2865        let stmt = parse("User filter .id not in (Order { .user_id })").unwrap();
2866        match stmt {
2867            Statement::Query(q) => match q.filter.unwrap() {
2868                Expr::InSubquery { negated, .. } => assert!(negated),
2869                other => panic!("expected InSubquery, got {other:?}"),
2870            },
2871            _ => panic!("expected query"),
2872        }
2873    }
2874
2875    #[test]
2876    fn test_parse_in_literal_list_still_works() {
2877        // Ensure existing IN (literal) parsing isn't broken
2878        let stmt = parse("User filter .age in (25, 30, 35)").unwrap();
2879        match stmt {
2880            Statement::Query(q) => match q.filter.unwrap() {
2881                Expr::InList { list, negated, .. } => {
2882                    assert!(!negated);
2883                    assert_eq!(list.len(), 3);
2884                }
2885                other => panic!("expected InList, got {other:?}"),
2886            },
2887            _ => panic!("expected query"),
2888        }
2889    }
2890
2891    // ---- Materialized view parser tests ------------------------------------
2892
2893    #[test]
2894    fn test_parse_create_view() {
2895        let stmt = parse("materialize OldUsers as User filter .age > 28").unwrap();
2896        match stmt {
2897            Statement::CreateView(cv) => {
2898                assert_eq!(cv.name, "OldUsers");
2899                assert_eq!(cv.query.source, "User");
2900                assert!(cv.query.filter.is_some());
2901                assert!(!cv.query_text.is_empty());
2902            }
2903            _ => panic!("expected CreateView"),
2904        }
2905    }
2906
2907    #[test]
2908    fn test_parse_create_view_with_projection() {
2909        let stmt = parse("materialize UserNames as User { .name }").unwrap();
2910        match stmt {
2911            Statement::CreateView(cv) => {
2912                assert_eq!(cv.name, "UserNames");
2913                assert!(cv.query.projection.is_some());
2914            }
2915            _ => panic!("expected CreateView"),
2916        }
2917    }
2918
2919    #[test]
2920    fn test_parse_refresh_view() {
2921        let stmt = parse("refresh OldUsers").unwrap();
2922        match stmt {
2923            Statement::RefreshView(rv) => {
2924                assert_eq!(rv.name, "OldUsers");
2925            }
2926            _ => panic!("expected RefreshView"),
2927        }
2928    }
2929
2930    #[test]
2931    fn test_parse_drop_view() {
2932        let stmt = parse("drop view OldUsers").unwrap();
2933        match stmt {
2934            Statement::DropView(dv) => {
2935                assert_eq!(dv.name, "OldUsers");
2936            }
2937            _ => panic!("expected DropView"),
2938        }
2939    }
2940
2941    #[test]
2942    fn test_parse_drop_table_still_works() {
2943        let stmt = parse("drop Users").unwrap();
2944        match stmt {
2945            Statement::DropTable(dt) => {
2946                assert_eq!(dt.table, "Users");
2947            }
2948            _ => panic!("expected DropTable"),
2949        }
2950    }
2951
2952    #[test]
2953    fn test_parse_union() {
2954        let stmt = parse("User union Order").unwrap();
2955        match stmt {
2956            Statement::Union(u) => {
2957                assert!(!u.all);
2958                match *u.left {
2959                    Statement::Query(_) => {}
2960                    _ => panic!("expected Query on left"),
2961                }
2962                match *u.right {
2963                    Statement::Query(_) => {}
2964                    _ => panic!("expected Query on right"),
2965                }
2966            }
2967            _ => panic!("expected Union"),
2968        }
2969    }
2970
2971    #[test]
2972    fn test_parse_union_all() {
2973        let stmt = parse("User union all Order").unwrap();
2974        match stmt {
2975            Statement::Union(u) => {
2976                assert!(u.all, "expected UNION ALL");
2977                match *u.left {
2978                    Statement::Query(_) => {}
2979                    _ => panic!("expected Query on left"),
2980                }
2981                match *u.right {
2982                    Statement::Query(_) => {}
2983                    _ => panic!("expected Query on right"),
2984                }
2985            }
2986            _ => panic!("expected Union"),
2987        }
2988    }
2989
2990    #[test]
2991    fn test_parse_union_chain() {
2992        // Left-associative: A union B union C => Union(Union(A, B), C)
2993        let stmt = parse("User union Order union Product").unwrap();
2994        match stmt {
2995            Statement::Union(outer) => {
2996                assert!(!outer.all);
2997                // Right side is Product
2998                match *outer.right {
2999                    Statement::Query(q) => assert_eq!(q.source, "Product"),
3000                    _ => panic!("expected Query(Product) on right"),
3001                }
3002                // Left side is Union(User, Order)
3003                match *outer.left {
3004                    Statement::Union(inner) => {
3005                        assert!(!inner.all);
3006                        match *inner.left {
3007                            Statement::Query(q) => assert_eq!(q.source, "User"),
3008                            _ => panic!("expected Query(User)"),
3009                        }
3010                        match *inner.right {
3011                            Statement::Query(q) => assert_eq!(q.source, "Order"),
3012                            _ => panic!("expected Query(Order)"),
3013                        }
3014                    }
3015                    _ => panic!("expected inner Union"),
3016                }
3017            }
3018            _ => panic!("expected Union"),
3019        }
3020    }
3021
3022    #[test]
3023    fn test_parse_union_with_filter() {
3024        let stmt = parse("User filter .age > 10 union Order filter .total > 50").unwrap();
3025        match stmt {
3026            Statement::Union(u) => {
3027                assert!(!u.all);
3028                // Both sides should be queries (the filter is part of each query)
3029                match *u.left {
3030                    Statement::Query(q) => {
3031                        assert_eq!(q.source, "User");
3032                        assert!(q.filter.is_some());
3033                    }
3034                    _ => panic!("expected Query on left"),
3035                }
3036                match *u.right {
3037                    Statement::Query(q) => {
3038                        assert_eq!(q.source, "Order");
3039                        assert!(q.filter.is_some());
3040                    }
3041                    _ => panic!("expected Query on right"),
3042                }
3043            }
3044            _ => panic!("expected Union"),
3045        }
3046    }
3047
3048    #[test]
3049    fn test_parse_count_distinct_standalone() {
3050        let stmt = parse("count(distinct User { .name })").unwrap();
3051        match stmt {
3052            Statement::Query(q) => {
3053                let agg = q.aggregation.unwrap();
3054                assert_eq!(agg.function, AggFunc::CountDistinct);
3055                assert_eq!(agg.field.as_deref(), Some("name"));
3056            }
3057            _ => panic!("expected Query"),
3058        }
3059    }
3060
3061    #[test]
3062    fn test_parse_count_distinct_in_projection() {
3063        let stmt = parse("User group .dept { .dept, count(distinct .name) }").unwrap();
3064        match stmt {
3065            Statement::Query(q) => {
3066                let proj = q.projection.unwrap();
3067                assert_eq!(proj.len(), 2);
3068                match &proj[1].expr {
3069                    Expr::FunctionCall(func, _) => {
3070                        assert_eq!(*func, AggFunc::CountDistinct);
3071                    }
3072                    _ => panic!("expected FunctionCall"),
3073                }
3074            }
3075            _ => panic!("expected Query"),
3076        }
3077    }
3078
3079    // ---- Window function parser tests ----------------------------------------
3080
3081    #[test]
3082    fn test_parse_window_row_number_order() {
3083        let stmt = parse("User { .name, rn: row_number() over (order .age) }").unwrap();
3084        match stmt {
3085            Statement::Query(q) => {
3086                let proj = q.projection.unwrap();
3087                assert_eq!(proj.len(), 2);
3088                assert_eq!(proj[1].alias.as_deref(), Some("rn"));
3089                match &proj[1].expr {
3090                    Expr::Window {
3091                        function,
3092                        args,
3093                        partition_by,
3094                        order_by,
3095                    } => {
3096                        assert_eq!(*function, WindowFunc::RowNumber);
3097                        assert!(args.is_empty());
3098                        assert!(partition_by.is_empty());
3099                        assert_eq!(order_by.len(), 1);
3100                        assert_eq!(order_by[0].field, "age");
3101                        assert!(!order_by[0].descending);
3102                    }
3103                    other => panic!("expected Window, got {other:?}"),
3104                }
3105            }
3106            _ => panic!("expected query"),
3107        }
3108    }
3109
3110    #[test]
3111    fn test_parse_window_sum_partition_order() {
3112        let stmt =
3113            parse("User { .name, s: sum(.salary) over (partition .dept order .salary) }").unwrap();
3114        match stmt {
3115            Statement::Query(q) => {
3116                let proj = q.projection.unwrap();
3117                assert_eq!(proj.len(), 2);
3118                assert_eq!(proj[1].alias.as_deref(), Some("s"));
3119                match &proj[1].expr {
3120                    Expr::Window {
3121                        function,
3122                        args,
3123                        partition_by,
3124                        order_by,
3125                    } => {
3126                        assert_eq!(*function, WindowFunc::Sum);
3127                        assert_eq!(args.len(), 1);
3128                        assert!(matches!(&args[0], Expr::Field(f) if f == "salary"));
3129                        assert_eq!(partition_by, &["dept"]);
3130                        assert_eq!(order_by.len(), 1);
3131                        assert_eq!(order_by[0].field, "salary");
3132                        assert!(!order_by[0].descending);
3133                    }
3134                    other => panic!("expected Window, got {other:?}"),
3135                }
3136            }
3137            _ => panic!("expected query"),
3138        }
3139    }
3140
3141    #[test]
3142    fn test_parse_window_rank_desc() {
3143        let stmt =
3144            parse("User { .dept, .salary, r: rank() over (partition .dept order .salary desc) }")
3145                .unwrap();
3146        match stmt {
3147            Statement::Query(q) => {
3148                let proj = q.projection.unwrap();
3149                assert_eq!(proj.len(), 3);
3150                match &proj[2].expr {
3151                    Expr::Window {
3152                        function,
3153                        partition_by,
3154                        order_by,
3155                        ..
3156                    } => {
3157                        assert_eq!(*function, WindowFunc::Rank);
3158                        assert_eq!(partition_by, &["dept"]);
3159                        assert_eq!(order_by.len(), 1);
3160                        assert!(order_by[0].descending);
3161                    }
3162                    other => panic!("expected Window, got {other:?}"),
3163                }
3164            }
3165            _ => panic!("expected query"),
3166        }
3167    }
3168
3169    #[test]
3170    fn test_parse_window_dense_rank() {
3171        let stmt = parse("User { .name, dr: dense_rank() over (order .score desc) }").unwrap();
3172        match stmt {
3173            Statement::Query(q) => {
3174                let proj = q.projection.unwrap();
3175                assert_eq!(proj.len(), 2);
3176                match &proj[1].expr {
3177                    Expr::Window { function, .. } => {
3178                        assert_eq!(*function, WindowFunc::DenseRank);
3179                    }
3180                    other => panic!("expected Window, got {other:?}"),
3181                }
3182            }
3183            _ => panic!("expected query"),
3184        }
3185    }
3186
3187    #[test]
3188    fn test_parse_sum_without_over_is_aggregate() {
3189        // sum(.salary) alone (no `over`) stays as FunctionCall, not Window.
3190        let stmt = parse("User group .dept { .dept, total: sum(.salary) }").unwrap();
3191        match stmt {
3192            Statement::Query(q) => {
3193                let proj = q.projection.unwrap();
3194                assert_eq!(proj.len(), 2);
3195                match &proj[1].expr {
3196                    Expr::FunctionCall(AggFunc::Sum, _) => {} // correct
3197                    other => panic!("expected FunctionCall(Sum), got {other:?}"),
3198                }
3199            }
3200            _ => panic!("expected query"),
3201        }
3202    }
3203
3204    #[test]
3205    fn test_nesting_depth_limit() {
3206        // Build a deeply nested parenthesized expression that exceeds MAX_NESTING_DEPTH.
3207        let mut query = String::from("User filter ");
3208        for _ in 0..70 {
3209            query.push('(');
3210        }
3211        query.push_str(".age > 1");
3212        for _ in 0..70 {
3213            query.push(')');
3214        }
3215        let result = parse(&query);
3216        assert!(result.is_err());
3217        let err = result.unwrap_err();
3218        assert!(
3219            err.message().contains("nesting depth"),
3220            "expected nesting depth error, got: {}",
3221            err.message()
3222        );
3223    }
3224
3225    #[test]
3226    fn test_moderate_nesting_succeeds() {
3227        // 10 levels of nesting should be fine.
3228        let mut query = String::from("User filter ");
3229        for _ in 0..10 {
3230            query.push('(');
3231        }
3232        query.push_str(".age > 1");
3233        for _ in 0..10 {
3234            query.push(')');
3235        }
3236        assert!(parse(&query).is_ok());
3237    }
3238
3239    /// Regression for issue #26: `fuzz_parser` crashed on the 3-byte input
3240    /// `nn{` — the projection loop consumed the Eof token and then indexed
3241    /// past the end of `tokens`. Must return an error instead.
3242    #[test]
3243    fn test_parse_fuzz_repro_projection_eof() {
3244        let err = parse("nn{").expect_err("unterminated projection must error, not panic");
3245        let _ = err.message();
3246    }
3247
3248    /// Regression for issue #26: `fuzz_roundtrip` tripped the same bug with
3249    /// the 2-byte input `z{`.
3250    #[test]
3251    fn test_parse_fuzz_repro_short_projection_eof() {
3252        let err = parse("z{").expect_err("unterminated projection must error, not panic");
3253        let _ = err.message();
3254    }
3255}