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