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