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