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