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