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