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