Skip to main content

reddb_server/storage/query/parser/
expr.rs

1//! Pratt-style parser for the Fase 2 `Expr` AST.
2//!
3//! This module is the Week 2 deliverable of the parser v2 refactor
4//! tracked in `/home/cyber/.claude/plans/squishy-mixing-honey.md`.
5//! It produces `ast::Expr` trees with proper operator precedence,
6//! `Span` tracking from the lexer, and support for the full set of
7//! unary / binary / postfix operators the existing hand-rolled
8//! projection climb covers in Fase 1.3 — plus the missing pieces
9//! (CASE, CAST, parenthesised subexprs, IS NULL, IN, BETWEEN).
10//!
11//! # Design notes
12//!
13//! The parser is now the canonical entry point for SQL expression
14//! parsing in the table-query flow:
15//! - `SELECT` projections parse through `Parser::parse_expr`
16//! - `WHERE` / `HAVING` operands parse through `Parser::parse_expr`
17//! - `ORDER BY` expressions parse through `Parser::parse_expr`
18//!
19//! Some legacy AST slots are still adapter-based (`Projection`,
20//! `Filter`, `GROUP BY` strings), so statement parsing still lowers
21//! `Expr` trees into those older shapes at the boundary.
22//!
23//! # Precedence table (matches PG gram.y modulo features we don't have)
24//!
25//! ```text
26//! prec  operators
27//! ----  ----------------------------------
28//!  10   OR
29//!  20   AND
30//!  25   NOT                      (prefix)
31//!  30   = <> < <= > >=           (comparison)
32//!  32   IS NULL / IS NOT NULL    (postfix)
33//!  33   BETWEEN … AND …          (postfix)
34//!  34   IN (…)                   (postfix)
35//!  40   ||                       (string concat)
36//!  50   + -                      (additive)
37//!  60   * / %                    (multiplicative)
38//!  70   -                        (unary negation)
39//!  80   ::type  CAST(…AS type)   (explicit type coercion)
40//! ```
41//!
42//! Higher precedence binds tighter. The climb uses the classic
43//! "min-precedence" algorithm — `parse_expr_prec(min)` loops consuming
44//! any infix operator whose precedence is ≥ `min`, recursing with
45//! `prec + 1` on the right-hand side for left-associativity.
46
47use super::super::ast::{BinOp, Expr, ExprSubquery, FieldRef, Span, UnaryOp};
48use super::super::lexer::Token;
49use super::error::ParseError;
50use super::Parser;
51use super::PlaceholderMode;
52use crate::storage::schema::{DataType, Value};
53
54fn is_duration_unit(unit: &str) -> bool {
55    matches!(
56        unit.to_ascii_lowercase().as_str(),
57        "ms" | "msec"
58            | "millisecond"
59            | "milliseconds"
60            | "s"
61            | "sec"
62            | "secs"
63            | "second"
64            | "seconds"
65            | "m"
66            | "min"
67            | "mins"
68            | "minute"
69            | "minutes"
70            | "h"
71            | "hr"
72            | "hrs"
73            | "hour"
74            | "hours"
75            | "d"
76            | "day"
77            | "days"
78    )
79}
80
81fn keyword_function_name(token: &Token) -> Option<&'static str> {
82    match token {
83        Token::Count => Some("COUNT"),
84        Token::Sum => Some("SUM"),
85        Token::Avg => Some("AVG"),
86        Token::Min => Some("MIN"),
87        Token::Max => Some("MAX"),
88        Token::First => Some("FIRST"),
89        Token::Last => Some("LAST"),
90        Token::Left => Some("LEFT"),
91        Token::Right => Some("RIGHT"),
92        Token::Contains => Some("CONTAINS"),
93        Token::Kv => Some("KV"),
94        _ => None,
95    }
96}
97
98/// Whether `name` may appear as the function in `fn(...) OVER (...)`.
99/// Window-only functions plus the standard aggregates (which behave as
100/// window aggregates when an OVER clause is attached). Mirrored loosely
101/// from PG's pg_proc catalog — slice 7a only validates lexical eligibility,
102/// runtime support arrives with the analytics executor.
103fn is_window_eligible_function(name: &str) -> bool {
104    matches!(
105        name.to_ascii_uppercase().as_str(),
106        // Window-only.
107        "LAG"
108            | "LEAD"
109            | "ROW_NUMBER"
110            | "RANK"
111            | "DENSE_RANK"
112            | "PERCENT_RANK"
113            | "CUME_DIST"
114            | "NTILE"
115            | "FIRST_VALUE"
116            | "LAST_VALUE"
117            | "NTH_VALUE"
118            // Aggregates valid in window position.
119            | "COUNT"
120            | "SUM"
121            | "AVG"
122            | "MIN"
123            | "MAX"
124            | "STDDEV"
125            | "VARIANCE"
126            | "MEDIAN"
127            | "PERCENTILE"
128            | "GROUP_CONCAT"
129            | "STRING_AGG"
130            | "FIRST"
131            | "LAST"
132            | "ARRAY_AGG"
133            | "COUNT_DISTINCT"
134    )
135}
136
137fn bare_zero_arg_function_name(name: &str) -> Option<&'static str> {
138    match name.to_ascii_uppercase().as_str() {
139        "CURRENT_TIMESTAMP" => Some("CURRENT_TIMESTAMP"),
140        "CURRENT_DATE" => Some("CURRENT_DATE"),
141        "CURRENT_TIME" => Some("CURRENT_TIME"),
142        _ => None,
143    }
144}
145
146impl<'a> Parser<'a> {
147    /// Parse a complete expression at the lowest precedence level.
148    /// Entry point for every caller that wants an `Expr` tree.
149    pub fn parse_expr(&mut self) -> Result<Expr, ParseError> {
150        self.parse_expr_prec(0)
151    }
152
153    pub(crate) fn parse_expr_with_min_precedence(
154        &mut self,
155        min_prec: u8,
156    ) -> Result<Expr, ParseError> {
157        self.parse_expr_prec(min_prec)
158    }
159
160    /// Continue parsing an expression after the caller has already
161    /// materialized the left-hand side atom.
162    pub(crate) fn continue_expr(&mut self, left: Expr, min_prec: u8) -> Result<Expr, ParseError> {
163        self.parse_expr_suffix(left, min_prec)
164    }
165
166    /// Pratt climb: parse a unary atom then consume any infix operators
167    /// whose precedence meets or exceeds `min_prec`.
168    fn parse_expr_prec(&mut self, min_prec: u8) -> Result<Expr, ParseError> {
169        // Depth guard: every recursive descent point in the expr
170        // grammar bottoms out here, so checking once is enough to
171        // catch deeply nested literals like `((((((1))))))` and
172        // boolean chains like `NOT NOT NOT NOT … x`.
173        self.enter_depth()?;
174        let result = (|| {
175            let left = self.parse_expr_unary()?;
176            self.parse_expr_suffix(left, min_prec)
177        })();
178        self.exit_depth();
179        result
180    }
181
182    fn parse_expr_suffix(&mut self, mut left: Expr, min_prec: u8) -> Result<Expr, ParseError> {
183        loop {
184            let Some((op, prec)) = self.peek_binop() else {
185                // Not a standard infix op — check for postfix forms.
186                if min_prec <= 32 {
187                    if let Some(node) = self.try_parse_postfix(&left)? {
188                        left = node;
189                        continue;
190                    }
191                }
192                break;
193            };
194            if prec < min_prec {
195                break;
196            }
197            self.advance()?; // consume the operator token
198            let start_span = self.span_start_of(&left);
199            let rhs = self.parse_expr_prec(prec + 1)?;
200            let end_span = self.span_end_of(&rhs);
201            left = Expr::BinaryOp {
202                op,
203                lhs: Box::new(left),
204                rhs: Box::new(rhs),
205                span: Span::new(start_span, end_span),
206            };
207        }
208        Ok(left)
209    }
210
211    /// Parse a unary-prefix expression or drop through to the atomic
212    /// factor. Handles `NOT`, unary `-`, and `+` (no-op sign).
213    fn parse_expr_unary(&mut self) -> Result<Expr, ParseError> {
214        match self.peek() {
215            Token::Not => {
216                let start = self.position();
217                self.advance()?;
218                let operand = self.parse_expr_prec(25)?;
219                let end = self.span_end_of(&operand);
220                Ok(Expr::UnaryOp {
221                    op: UnaryOp::Not,
222                    operand: Box::new(operand),
223                    span: Span::new(start, end),
224                })
225            }
226            Token::Dash => {
227                let start = self.position();
228                self.advance()?;
229                let operand = self.parse_expr_prec(70)?;
230                let end = self.span_end_of(&operand);
231                Ok(Expr::UnaryOp {
232                    op: UnaryOp::Neg,
233                    operand: Box::new(operand),
234                    span: Span::new(start, end),
235                })
236            }
237            Token::Plus => {
238                // Unary plus is a no-op. Consume and recurse.
239                self.advance()?;
240                self.parse_expr_prec(70)
241            }
242            _ => self.parse_expr_factor(),
243        }
244    }
245
246    /// Parse a single atomic expression factor: literal, column ref,
247    /// parenthesised subexpression, CAST, CASE, or function call.
248    fn parse_expr_factor(&mut self) -> Result<Expr, ParseError> {
249        let start = self.position();
250
251        // Parenthesised subexpression: `( expr )`
252        if self.consume(&Token::LParen)? {
253            if self.check(&Token::Select) {
254                let query = self.parse_select_query()?;
255                self.expect(Token::RParen)?;
256                return Ok(Expr::Subquery {
257                    query: ExprSubquery {
258                        query: Box::new(query),
259                    },
260                    span: Span::new(start, self.position()),
261                });
262            }
263            let inner = self.parse_expr_prec(0)?;
264            self.expect(Token::RParen)?;
265            return Ok(inner);
266        }
267
268        // Literal: true / false / null
269        if self.consume(&Token::True)? {
270            return Ok(Expr::Literal {
271                value: Value::Boolean(true),
272                span: Span::new(start, self.position()),
273            });
274        }
275        if self.consume(&Token::False)? {
276            return Ok(Expr::Literal {
277                value: Value::Boolean(false),
278                span: Span::new(start, self.position()),
279            });
280        }
281        if self.consume(&Token::Null)? {
282            return Ok(Expr::Literal {
283                value: Value::Null,
284                span: Span::new(start, self.position()),
285            });
286        }
287
288        // Numeric literals — with optional duration-unit suffix (e.g. `5m`, `10s`, `2h`).
289        // Duration literals are emitted as Value::Text so downstream code sees "5m" verbatim
290        // (matching the legacy Projection::Column("LIT:5m") path used by time_bucket).
291        if let Token::Integer(n) = *self.peek() {
292            self.advance()?;
293            if let Token::Ident(ref unit) = *self.peek() {
294                if is_duration_unit(unit) {
295                    let duration = format!("{n}{}", unit.to_ascii_lowercase());
296                    self.advance()?;
297                    return Ok(Expr::Literal {
298                        value: Value::text(duration),
299                        span: Span::new(start, self.position()),
300                    });
301                }
302            }
303            return Ok(Expr::Literal {
304                value: Value::Integer(n),
305                span: Span::new(start, self.position()),
306            });
307        }
308        if let Token::Float(n) = *self.peek() {
309            self.advance()?;
310            return Ok(Expr::Literal {
311                value: Value::Float(n),
312                span: Span::new(start, self.position()),
313            });
314        }
315        if let Token::String(ref s) = *self.peek() {
316            let text = s.clone();
317            self.advance()?;
318            return Ok(Expr::Literal {
319                value: Value::text(text),
320                span: Span::new(start, self.position()),
321            });
322        }
323
324        // JSON object `{…}` and array `[…]` literals — delegate to the DML literal parser
325        // which already handles the full JSON value grammar including nested objects.
326        // `JsonLiteral` is the strict-JSON variant emitted by the lexer's sub-mode
327        // when `{` is followed by `"`; both shapes route through `parse_literal_value`.
328        if matches!(
329            self.peek(),
330            Token::LBrace | Token::LBracket | Token::JsonLiteral(_)
331        ) {
332            let value = self
333                .parse_literal_value()
334                .map_err(|e| ParseError::new(e.message, self.position()))?;
335            return Ok(Expr::Literal {
336                value,
337                span: Span::new(start, self.position()),
338            });
339        }
340
341        // `?` positional placeholder — auto-numbered left-to-right.
342        // Immediate `?N` uses an explicit 1-based index. Mixing with
343        // `$N` in one statement is rejected.
344        if self.check(&Token::Question) {
345            let (index, span) = self.parse_question_param_index()?;
346            return Ok(Expr::Parameter { index, span });
347        }
348
349        if self.consume(&Token::Dollar)? {
350            // `$N` positional parameter placeholder (1-based in source,
351            // 0-based in the AST so it matches `Vec<Value>` indexing).
352            // Rejected at parse time when N < 1; gaps and arity are
353            // validated by the binder once the full statement is parsed.
354            if let Token::Integer(n) = *self.peek() {
355                if n < 1 {
356                    return Err(ParseError::new(
357                        "placeholder index must be >= 1".to_string(),
358                        self.position(),
359                    ));
360                }
361                if self.placeholder_mode == PlaceholderMode::Question {
362                    return Err(ParseError::new(
363                        "cannot mix `?` and `$N` placeholders in one statement".to_string(),
364                        self.position(),
365                    ));
366                }
367                self.placeholder_mode = PlaceholderMode::Dollar;
368                self.advance()?;
369                return Ok(Expr::Parameter {
370                    index: (n - 1) as usize,
371                    span: Span::new(start, self.position()),
372                });
373            }
374            let path = self.parse_dollar_ref_path()?;
375            let path_lc = path.to_ascii_lowercase();
376            let (name, key) = if let Some(rest) = path_lc.strip_prefix("secret.") {
377                ("__SECRET_REF", format!("red.vault/{rest}"))
378            } else if path_lc.starts_with("red.secret.") {
379                let rest = path_lc.trim_start_matches("red.secret.");
380                ("__SECRET_REF", format!("red.vault/{rest}"))
381            } else if let Some(rest) = path_lc.strip_prefix("config.") {
382                ("CONFIG", format!("red.config/{rest}"))
383            } else if path_lc.starts_with("red.config.") {
384                let rest = path_lc.trim_start_matches("red.config.");
385                ("CONFIG", format!("red.config/{rest}"))
386            } else {
387                return Err(ParseError::new(
388                    format!(
389                        "unknown $ reference `${path}`; expected $secret.*, $red.secret.*, $config.*, or $red.config.*"
390                    ),
391                    self.position(),
392                ));
393            };
394            return Ok(Expr::FunctionCall {
395                name: name.to_string(),
396                args: vec![Expr::Literal {
397                    value: Value::text(key),
398                    span: Span::new(start, self.position()),
399                }],
400                span: Span::new(start, self.position()),
401            });
402        }
403
404        if let Some(name) = keyword_function_name(self.peek()) {
405            if matches!(self.peek_next()?, Token::LParen) {
406                self.advance()?; // consume the keyword token
407                return self.parse_function_call_expr_with_name(start, name.to_string());
408            }
409        }
410
411        // Identifier-led constructs: function call, CAST, CASE, column.
412        //
413        // We commit to consuming the identifier immediately and then
414        // inspect the NEXT token to decide shape. This avoids needing
415        // two-token lookahead on the parser. If the next token is `(`
416        // it's a function call; if `.` it's a qualified column ref;
417        // otherwise it's a bare column ref.
418        if let Token::Ident(ref name) = *self.peek() {
419            let name_upper = name.to_uppercase();
420
421            // CAST(expr AS type) — must test before consuming because
422            // CAST is not a reserved keyword; users could legitimately
423            // have a column literally named `cast`. Distinguish by
424            // looking at whether the identifier equals CAST AND is
425            // immediately followed by `(`. Since we can't two-step
426            // lookahead, handle CAST by parsing the ident, then if the
427            // uppercased name is CAST and the next token is `(`,
428            // switch to the CAST form; otherwise the saved name
429            // becomes the first segment of a column ref.
430            if name_upper == "CASE" {
431                return self.parse_case_expr(start);
432            }
433
434            let saved_name = name.clone();
435            self.advance()?; // consume the identifier unconditionally
436
437            // Function call / CAST: IDENT (
438            if matches!(self.peek(), Token::LParen) {
439                return self.parse_function_call_expr_with_name(start, saved_name);
440            }
441
442            if let Some(function_name) = bare_zero_arg_function_name(&saved_name) {
443                let end = self.position();
444                return Ok(Expr::FunctionCall {
445                    name: function_name.to_string(),
446                    args: Vec::new(),
447                    span: Span::new(start, end),
448                });
449            }
450
451            // Qualified column: IDENT.IDENT[.IDENT …]
452            if matches!(self.peek(), Token::Dot) {
453                let mut segments = vec![saved_name];
454                while self.consume(&Token::Dot)? {
455                    segments.push(self.expect_ident_or_keyword()?);
456                }
457                let field = FieldRef::TableColumn {
458                    table: segments.remove(0),
459                    column: segments.join("."),
460                };
461                let end = self.position();
462                return Ok(Expr::Column {
463                    field,
464                    span: Span::new(start, end),
465                });
466            }
467
468            // Bare column reference with empty table name.
469            let field = FieldRef::TableColumn {
470                table: String::new(),
471                column: saved_name,
472            };
473            let end = self.position();
474            return Ok(Expr::Column {
475                field,
476                span: Span::new(start, end),
477            });
478        }
479
480        // Default: column reference (optionally qualified: table.column).
481        // Reached only when the leading token is not an Ident. Falls
482        // through to parse_field_ref which handles keyword-shaped
483        // column names.
484        let field = self.parse_field_ref()?;
485        let end = self.position();
486        Ok(Expr::Column {
487            field,
488            span: Span::new(start, end),
489        })
490    }
491
492    fn parse_dollar_ref_path(&mut self) -> Result<String, ParseError> {
493        let mut path = self.expect_ident_or_keyword()?;
494        while self.consume(&Token::Dot)? {
495            let next = self.expect_ident_or_keyword()?;
496            path = format!("{path}.{next}");
497        }
498        Ok(path)
499    }
500
501    fn parse_function_call_expr_with_name(
502        &mut self,
503        start: crate::storage::query::lexer::Position,
504        function_name: String,
505    ) -> Result<Expr, ParseError> {
506        let call = self.parse_function_call_expr_with_name_inner(start, function_name)?;
507        // Issue #589 slice 7a: `fn(args) OVER (...)` lifts a plain
508        // FunctionCall into a WindowFunctionCall carrying the OVER
509        // clause. CAST and other shapes that don't return a
510        // FunctionCall are rejected by `parse_over_clause_for` so the
511        // user gets a clear error rather than silent acceptance.
512        if matches!(self.peek(), Token::Over) {
513            return self.lift_to_window_call(start, call);
514        }
515        Ok(call)
516    }
517
518    fn parse_function_call_expr_with_name_inner(
519        &mut self,
520        start: crate::storage::query::lexer::Position,
521        function_name: String,
522    ) -> Result<Expr, ParseError> {
523        self.expect(Token::LParen)?;
524
525        if function_name.eq_ignore_ascii_case("CAST") {
526            let inner = self.parse_expr_prec(0)?;
527            self.expect(Token::As)?;
528            let type_name = self.expect_ident_or_keyword()?;
529            self.expect(Token::RParen)?;
530            let end = self.position();
531            let Some(target) = DataType::from_sql_name(&type_name) else {
532                return Err(ParseError::new(
533                    // F-05: `type_name` is caller-controlled identifier text.
534                    // Render via `{:?}` so embedded CR/LF/NUL/quotes are
535                    // escaped before reaching downstream serialization sinks.
536                    format!("unknown type name {type_name:?} in CAST"),
537                    self.position(),
538                ));
539            };
540            return Ok(Expr::Cast {
541                inner: Box::new(inner),
542                target,
543                span: Span::new(start, end),
544            });
545        }
546
547        if function_name.eq_ignore_ascii_case("TRIM") {
548            let (name, args) = self.parse_trim_expr_args()?;
549            self.expect(Token::RParen)?;
550            let end = self.position();
551            return Ok(Expr::FunctionCall {
552                name,
553                args,
554                span: Span::new(start, end),
555            });
556        }
557
558        if function_name.eq_ignore_ascii_case("POSITION") {
559            let args = self.parse_position_expr_args()?;
560            self.expect(Token::RParen)?;
561            let end = self.position();
562            return Ok(Expr::FunctionCall {
563                name: function_name,
564                args,
565                span: Span::new(start, end),
566            });
567        }
568
569        if function_name.eq_ignore_ascii_case("SUBSTRING") {
570            let args = self.parse_substring_expr_args()?;
571            self.expect(Token::RParen)?;
572            let end = self.position();
573            return Ok(Expr::FunctionCall {
574                name: function_name,
575                args,
576                span: Span::new(start, end),
577            });
578        }
579
580        if function_name.eq_ignore_ascii_case("COUNT") {
581            if self.consume(&Token::Distinct)? {
582                let arg = self.parse_expr_prec(0)?;
583                self.expect(Token::RParen)?;
584                let end = self.position();
585                return Ok(Expr::FunctionCall {
586                    name: "COUNT_DISTINCT".to_string(),
587                    args: vec![arg],
588                    span: Span::new(start, end),
589                });
590            }
591
592            if self.consume(&Token::Star)? {
593                self.expect(Token::RParen)?;
594                let end = self.position();
595                return Ok(Expr::FunctionCall {
596                    name: function_name,
597                    args: vec![Expr::Column {
598                        field: FieldRef::TableColumn {
599                            table: String::new(),
600                            column: "*".to_string(),
601                        },
602                        span: Span::synthetic(),
603                    }],
604                    span: Span::new(start, end),
605                });
606            }
607        }
608
609        // CONFIG()/KV() take bare dotted config paths as arguments
610        // (e.g. `CONFIG(red.ai.default.provider, openai)`,
611        // `KV(cfg, default.role, guest)`). Parsed through the generic
612        // expression grammar these become column references — and a
613        // keyword segment like `default` would be folded to `DEFAULT`,
614        // breaking the case-sensitive config-key lookup, while a
615        // source-free `SELECT CONFIG(...)` would fail with "unknown
616        // column". Capture each path-shaped argument as a lowercased
617        // string literal instead so it matches stored keys (which
618        // `SET CONFIG` also lowercases) and never resolves as a column.
619        if function_name.eq_ignore_ascii_case("CONFIG") || function_name.eq_ignore_ascii_case("KV")
620        {
621            let mut args = Vec::new();
622            if !self.check(&Token::RParen) {
623                loop {
624                    args.push(self.parse_config_kv_arg(start)?);
625                    if !self.consume(&Token::Comma)? {
626                        break;
627                    }
628                }
629            }
630            self.expect(Token::RParen)?;
631            let end = self.position();
632            return Ok(Expr::FunctionCall {
633                name: function_name,
634                args,
635                span: Span::new(start, end),
636            });
637        }
638
639        let mut args = Vec::new();
640        if !self.check(&Token::RParen) {
641            loop {
642                args.push(self.parse_expr_prec(0)?);
643                if !self.consume(&Token::Comma)? {
644                    break;
645                }
646            }
647        }
648        self.expect(Token::RParen)?;
649        let end = self.position();
650        Ok(Expr::FunctionCall {
651            name: function_name,
652            args,
653            span: Span::new(start, end),
654        })
655    }
656
657    /// Parse a single CONFIG()/KV() argument. A bare identifier or
658    /// dotted path (including keyword-shaped segments) becomes a
659    /// lowercased string literal — the config-key form. Anything else
660    /// (quoted string, number, `?`/`$N` placeholder, parenthesised
661    /// expression) falls through to the normal expression grammar so
662    /// dynamic defaults still work.
663    fn parse_config_kv_arg(
664        &mut self,
665        start: crate::storage::query::lexer::Position,
666    ) -> Result<Expr, ParseError> {
667        // Literals, placeholders and parenthesised sub-expressions are
668        // real expressions (dynamic defaults); everything else that can
669        // open an argument here is an identifier or keyword that forms a
670        // bare config path.
671        let mut is_expression_start = matches!(
672            self.peek(),
673            Token::String(_)
674                | Token::Integer(_)
675                | Token::Float(_)
676                | Token::Dollar
677                | Token::Question
678                | Token::LParen
679        );
680        // A bare identifier immediately followed by `(` is a nested
681        // function call (e.g. a dynamic default), not a config path.
682        if matches!(self.peek(), Token::Ident(_)) && matches!(self.peek_next()?, Token::LParen) {
683            is_expression_start = true;
684        }
685        if !is_expression_start && !self.check(&Token::RParen) {
686            let mut path = self.expect_ident_or_keyword()?;
687            while self.consume(&Token::Dot)? {
688                let next = self.expect_ident_or_keyword()?;
689                path = format!("{path}.{next}");
690            }
691            let end = self.position();
692            return Ok(Expr::Literal {
693                value: Value::text(path.to_ascii_lowercase()),
694                span: Span::new(start, end),
695            });
696        }
697        self.parse_expr_prec(0)
698    }
699
700    /// Wrap a freshly-parsed `Expr::FunctionCall` in
701    /// `Expr::WindowFunctionCall` by consuming the trailing `OVER (...)`
702    /// clause. The caller has already confirmed the next token is
703    /// `OVER`. Rejects:
704    /// - CAST(...) OVER (...) and other non-FunctionCall shapes.
705    /// - Function names that are neither window-only nor aggregates.
706    fn lift_to_window_call(
707        &mut self,
708        start: crate::storage::query::lexer::Position,
709        call: Expr,
710    ) -> Result<Expr, ParseError> {
711        let (name, args) = match call {
712            Expr::FunctionCall { name, args, .. } => (name, args),
713            other => {
714                return Err(ParseError::new(
715                    format!(
716                        "OVER may only follow a function call, got {:?}",
717                        std::mem::discriminant(&other)
718                    ),
719                    self.position(),
720                ));
721            }
722        };
723        if !is_window_eligible_function(&name) {
724            return Err(ParseError::new(
725                format!(
726                    "function `{}` cannot be used with an OVER clause; \
727                     expected a window function (LAG, LEAD, ROW_NUMBER, \
728                     RANK, DENSE_RANK) or an aggregate",
729                    name.to_uppercase()
730                ),
731                self.position(),
732            ));
733        }
734        let window = self.parse_over_clause()?;
735        let end = self.position();
736        Ok(Expr::WindowFunctionCall {
737            name,
738            args,
739            window,
740            span: Span::new(start, end),
741        })
742    }
743
744    /// Parse the `OVER ( [PARTITION BY ...] [ORDER BY ...] [frame] )`
745    /// clause. The leading `OVER` keyword is consumed here.
746    fn parse_over_clause(&mut self) -> Result<crate::storage::query::ast::WindowSpec, ParseError> {
747        self.expect(Token::Over)?;
748        self.expect(Token::LParen)?;
749
750        let mut spec = crate::storage::query::ast::WindowSpec::default();
751
752        if self.consume(&Token::Partition)? {
753            self.expect(Token::By)?;
754            loop {
755                spec.partition_by.push(self.parse_expr_prec(0)?);
756                if !self.consume(&Token::Comma)? {
757                    break;
758                }
759            }
760        }
761
762        if self.consume(&Token::Order)? {
763            self.expect(Token::By)?;
764            loop {
765                let expr = self.parse_expr_prec(0)?;
766                let ascending = if self.consume(&Token::Desc)? {
767                    false
768                } else {
769                    self.consume(&Token::Asc)?;
770                    true
771                };
772                // NULLS FIRST / LAST defaults mirror PG: nulls last for
773                // ASC, nulls first for DESC. Explicit clause overrides.
774                let mut nulls_first = !ascending;
775                if self.consume(&Token::Nulls)? {
776                    if self.consume(&Token::First)? {
777                        nulls_first = true;
778                    } else if self.consume(&Token::Last)? {
779                        nulls_first = false;
780                    } else {
781                        return Err(ParseError::new(
782                            "expected FIRST or LAST after NULLS".to_string(),
783                            self.position(),
784                        ));
785                    }
786                }
787                spec.order_by
788                    .push(crate::storage::query::ast::WindowOrderItem {
789                        expr,
790                        ascending,
791                        nulls_first,
792                    });
793                if !self.consume(&Token::Comma)? {
794                    break;
795                }
796            }
797        }
798
799        if matches!(self.peek(), Token::Rows | Token::Range) {
800            spec.frame = Some(self.parse_window_frame()?);
801        }
802
803        self.expect(Token::RParen)?;
804        Ok(spec)
805    }
806
807    fn parse_window_frame(
808        &mut self,
809    ) -> Result<crate::storage::query::ast::WindowFrame, ParseError> {
810        let unit = if self.consume(&Token::Rows)? {
811            crate::storage::query::ast::WindowFrameUnit::Rows
812        } else if self.consume(&Token::Range)? {
813            crate::storage::query::ast::WindowFrameUnit::Range
814        } else {
815            return Err(ParseError::new(
816                "expected ROWS or RANGE in window frame".to_string(),
817                self.position(),
818            ));
819        };
820
821        if self.consume(&Token::Between)? {
822            let start = self.parse_window_frame_bound()?;
823            self.expect(Token::And)?;
824            let end = self.parse_window_frame_bound()?;
825            Ok(crate::storage::query::ast::WindowFrame {
826                unit,
827                start,
828                end: Some(end),
829            })
830        } else {
831            let start = self.parse_window_frame_bound()?;
832            Ok(crate::storage::query::ast::WindowFrame {
833                unit,
834                start,
835                end: None,
836            })
837        }
838    }
839
840    fn parse_window_frame_bound(
841        &mut self,
842    ) -> Result<crate::storage::query::ast::WindowFrameBound, ParseError> {
843        use crate::storage::query::ast::WindowFrameBound;
844        if self.consume(&Token::Unbounded)? {
845            if self.consume(&Token::Preceding)? {
846                return Ok(WindowFrameBound::UnboundedPreceding);
847            }
848            if self.consume(&Token::Following)? {
849                return Ok(WindowFrameBound::UnboundedFollowing);
850            }
851            return Err(ParseError::new(
852                "expected PRECEDING or FOLLOWING after UNBOUNDED".to_string(),
853                self.position(),
854            ));
855        }
856        if self.consume(&Token::Current)? {
857            self.expect(Token::Row)?;
858            return Ok(WindowFrameBound::CurrentRow);
859        }
860        // Numeric / expression offset: `N PRECEDING` / `N FOLLOWING`.
861        let offset = self.parse_expr_prec(0)?;
862        if self.consume(&Token::Preceding)? {
863            return Ok(WindowFrameBound::Preceding(Box::new(offset)));
864        }
865        if self.consume(&Token::Following)? {
866            return Ok(WindowFrameBound::Following(Box::new(offset)));
867        }
868        Err(ParseError::new(
869            "expected PRECEDING or FOLLOWING after frame offset".to_string(),
870            self.position(),
871        ))
872    }
873
874    /// Parse `CASE WHEN cond THEN val [WHEN …] [ELSE val] END`.
875    /// Assumes the caller has already peeked `CASE`.
876    fn parse_case_expr(
877        &mut self,
878        start: crate::storage::query::lexer::Position,
879    ) -> Result<Expr, ParseError> {
880        self.advance()?; // consume CASE
881        let mut branches: Vec<(Expr, Expr)> = Vec::new();
882        loop {
883            if !self.consume_ident_ci("WHEN")? {
884                break;
885            }
886            let cond = self.parse_expr_prec(0)?;
887            if !self.consume_ident_ci("THEN")? {
888                return Err(ParseError::new(
889                    "expected THEN after CASE WHEN condition".to_string(),
890                    self.position(),
891                ));
892            }
893            let then_val = self.parse_expr_prec(0)?;
894            branches.push((cond, then_val));
895        }
896        if branches.is_empty() {
897            return Err(ParseError::new(
898                "CASE must have at least one WHEN branch".to_string(),
899                self.position(),
900            ));
901        }
902        let else_ = if self.consume_ident_ci("ELSE")? {
903            Some(Box::new(self.parse_expr_prec(0)?))
904        } else {
905            None
906        };
907        if !self.consume_ident_ci("END")? {
908            return Err(ParseError::new(
909                "expected END to close CASE expression".to_string(),
910                self.position(),
911            ));
912        }
913        let end = self.position();
914        Ok(Expr::Case {
915            branches,
916            else_,
917            span: Span::new(start, end),
918        })
919    }
920
921    fn parse_trim_expr_args(&mut self) -> Result<(String, Vec<Expr>), ParseError> {
922        let mut function_name = "TRIM".to_string();
923
924        if self.consume_ident_ci("LEADING")? {
925            function_name = "LTRIM".to_string();
926        } else if self.consume_ident_ci("TRAILING")? {
927            function_name = "RTRIM".to_string();
928        } else if self.consume_ident_ci("BOTH")? {
929            function_name = "TRIM".to_string();
930        }
931
932        if self.consume(&Token::From)? {
933            let source = self.parse_expr_prec(0)?;
934            return Ok((function_name, vec![source]));
935        }
936
937        let first = self.parse_expr_prec(0)?;
938
939        if self.consume(&Token::Comma)? {
940            let second = self.parse_expr_prec(0)?;
941            return Ok((function_name, vec![first, second]));
942        }
943
944        if self.consume(&Token::From)? {
945            let source = self.parse_expr_prec(0)?;
946            return Ok((function_name, vec![source, first]));
947        }
948
949        Ok((function_name, vec![first]))
950    }
951
952    /// PostgreSQL-style `POSITION(substr IN string)` or plain
953    /// `POSITION(substr, string)` lowered to the ordinary two-argument
954    /// function form.
955    fn parse_position_expr_args(&mut self) -> Result<Vec<Expr>, ParseError> {
956        // `IN` is also a postfix operator in the main expression grammar, so
957        // parse the first operand above postfix-IN precedence and then consume
958        // the function's `IN` keyword explicitly.
959        let needle = self.parse_expr_prec(35)?;
960        if !self.consume(&Token::Comma)? {
961            self.expect(Token::In)?;
962        }
963        let haystack = self.parse_expr_prec(0)?;
964        Ok(vec![needle, haystack])
965    }
966
967    /// PostgreSQL-style `SUBSTRING` syntax:
968    /// - `SUBSTRING(expr FROM start [FOR count])`
969    /// - `SUBSTRING(expr FOR count [FROM start])`
970    /// - plain function-call form `SUBSTRING(expr, start[, count])`
971    ///
972    /// The SQL-syntax variants are desugared to the comma-arg form so the
973    /// rest of the stack sees the same `Expr::FunctionCall` shape.
974    fn parse_substring_expr_args(&mut self) -> Result<Vec<Expr>, ParseError> {
975        let source = self.parse_expr_prec(0)?;
976
977        if self.consume(&Token::Comma)? {
978            let mut args = vec![source];
979            loop {
980                args.push(self.parse_expr_prec(0)?);
981                if !self.consume(&Token::Comma)? {
982                    break;
983                }
984            }
985            return Ok(args);
986        }
987
988        if self.consume(&Token::From)? {
989            let start = self.parse_expr_prec(0)?;
990            if self.consume(&Token::For)? {
991                let count = self.parse_expr_prec(0)?;
992                return Ok(vec![source, start, count]);
993            }
994            return Ok(vec![source, start]);
995        }
996
997        if self.consume(&Token::For)? {
998            let count = self.parse_expr_prec(0)?;
999            if self.consume(&Token::From)? {
1000                let start = self.parse_expr_prec(0)?;
1001                return Ok(vec![source, start, count]);
1002            }
1003            return Ok(vec![source, Expr::lit(Value::Integer(1)), count]);
1004        }
1005
1006        Ok(vec![source])
1007    }
1008
1009    /// Try to consume a postfix operator on top of the already-parsed
1010    /// `left` expression: `IS [NOT] NULL`, `[NOT] BETWEEN … AND …`,
1011    /// `[NOT] IN (…)`. Returns `Ok(None)` if no postfix follows.
1012    ///
1013    /// NOT at this position is unambiguous — prefix `NOT` is always
1014    /// consumed at `parse_expr_unary` level before reaching postfix.
1015    /// So seeing `NOT` here means the user wrote `x NOT BETWEEN …`
1016    /// or `x NOT IN …`; we consume it eagerly and require BETWEEN
1017    /// or IN to follow.
1018    fn try_parse_postfix(&mut self, left: &Expr) -> Result<Option<Expr>, ParseError> {
1019        let start = self.span_start_of(left);
1020
1021        // IS [NOT] NULL
1022        if self.consume(&Token::Is)? {
1023            let negated = self.consume(&Token::Not)?;
1024            self.expect(Token::Null)?;
1025            let end = self.position();
1026            return Ok(Some(Expr::IsNull {
1027                operand: Box::new(left.clone()),
1028                negated,
1029                span: Span::new(start, end),
1030            }));
1031        }
1032
1033        // Detect NOT BETWEEN / NOT IN. NOT is consumed eagerly — we
1034        // don't have two-token lookahead and the grammar guarantees
1035        // no other valid postfix starts with NOT.
1036        let negated = if matches!(self.peek(), Token::Not) {
1037            self.advance()?;
1038            if !matches!(self.peek(), Token::Between | Token::In) {
1039                return Err(ParseError::new(
1040                    "expected BETWEEN or IN after postfix NOT".to_string(),
1041                    self.position(),
1042                ));
1043            }
1044            true
1045        } else {
1046            false
1047        };
1048
1049        // BETWEEN low AND high
1050        if self.consume(&Token::Between)? {
1051            let low = self.parse_expr_prec(34)?;
1052            self.expect(Token::And)?;
1053            let high = self.parse_expr_prec(34)?;
1054            let end = self.position();
1055            return Ok(Some(Expr::Between {
1056                target: Box::new(left.clone()),
1057                low: Box::new(low),
1058                high: Box::new(high),
1059                negated,
1060                span: Span::new(start, end),
1061            }));
1062        }
1063
1064        // IN (v1, v2, …)
1065        if self.consume(&Token::In)? {
1066            self.expect(Token::LParen)?;
1067            let mut values = Vec::new();
1068            if self.check(&Token::Select) {
1069                let query = self.parse_select_query()?;
1070                values.push(Expr::Subquery {
1071                    query: ExprSubquery {
1072                        query: Box::new(query),
1073                    },
1074                    span: Span::new(self.span_start_of(left), self.position()),
1075                });
1076            } else if !self.check(&Token::RParen) {
1077                loop {
1078                    values.push(self.parse_expr_prec(0)?);
1079                    if !self.consume(&Token::Comma)? {
1080                        break;
1081                    }
1082                }
1083            }
1084            self.expect(Token::RParen)?;
1085            let end = self.position();
1086            return Ok(Some(Expr::InList {
1087                target: Box::new(left.clone()),
1088                values,
1089                negated,
1090                span: Span::new(start, end),
1091            }));
1092        }
1093
1094        if negated {
1095            // Unreachable because the early-return above already
1096            // validated NOT is followed by BETWEEN or IN. Guarded
1097            // to keep callers loud if the grammar grows later.
1098            return Err(ParseError::new(
1099                "internal: NOT consumed without BETWEEN/IN follow".to_string(),
1100                self.position(),
1101            ));
1102        }
1103        Ok(None)
1104    }
1105
1106    /// Peek the current token and translate it into a `BinOp` plus
1107    /// its precedence. Returns `None` if the token is not a recognised
1108    /// infix operator — the caller then tries postfix handling.
1109    fn peek_binop(&self) -> Option<(BinOp, u8)> {
1110        let op = match self.peek() {
1111            Token::Or => BinOp::Or,
1112            Token::And => BinOp::And,
1113            Token::Eq => BinOp::Eq,
1114            Token::Ne => BinOp::Ne,
1115            Token::Lt => BinOp::Lt,
1116            Token::Le => BinOp::Le,
1117            Token::Gt => BinOp::Gt,
1118            Token::Ge => BinOp::Ge,
1119            Token::DoublePipe => BinOp::Concat,
1120            Token::Plus => BinOp::Add,
1121            Token::Dash => BinOp::Sub,
1122            Token::Star => BinOp::Mul,
1123            Token::Slash => BinOp::Div,
1124            Token::Percent => BinOp::Mod,
1125            _ => return None,
1126        };
1127        Some((op, op.precedence()))
1128    }
1129
1130    /// Return the start position of an expression's span. Handles the
1131    /// synthetic case by falling back to the current parser cursor,
1132    /// which is good enough for the Pratt climb since the caller just
1133    /// parsed the atom.
1134    fn span_start_of(&self, expr: &Expr) -> crate::storage::query::lexer::Position {
1135        let s = expr.span();
1136        if s.is_synthetic() {
1137            self.position()
1138        } else {
1139            s.start
1140        }
1141    }
1142
1143    /// Return the end position of an expression's span — same
1144    /// synthetic fallback as `span_start_of`.
1145    fn span_end_of(&self, expr: &Expr) -> crate::storage::query::lexer::Position {
1146        let s = expr.span();
1147        if s.is_synthetic() {
1148            self.position()
1149        } else {
1150            s.end
1151        }
1152    }
1153}
1154
1155// Avoid `unused` lints in partial-migration builds where the analyzer
1156// still does not consume every expression shape directly.
1157#[allow(dead_code)]
1158fn _expr_module_used(_: Expr) {}
1159
1160#[cfg(test)]
1161mod tests {
1162    use super::*;
1163    use crate::storage::query::ast::FieldRef;
1164
1165    fn parse(input: &str) -> Expr {
1166        let mut parser = Parser::new(input).expect("lexer init");
1167        let expr = parser.parse_expr().expect("parse_expr");
1168        expr
1169    }
1170
1171    #[test]
1172    fn literal_integer() {
1173        let e = parse("42");
1174        match e {
1175            Expr::Literal {
1176                value: Value::Integer(42),
1177                ..
1178            } => {}
1179            other => panic!("expected Integer(42), got {other:?}"),
1180        }
1181    }
1182
1183    #[test]
1184    fn literal_float() {
1185        let e = parse("3.14");
1186        match e {
1187            Expr::Literal {
1188                value: Value::Float(f),
1189                ..
1190            } => assert!((f - 3.14).abs() < 1e-9),
1191            other => panic!("expected float literal, got {other:?}"),
1192        }
1193    }
1194
1195    #[test]
1196    fn literal_string() {
1197        let e = parse("'hello'");
1198        match e {
1199            Expr::Literal {
1200                value: Value::Text(ref s),
1201                ..
1202            } if s.as_ref() == "hello" => {}
1203            other => panic!("expected Text(hello), got {other:?}"),
1204        }
1205    }
1206
1207    #[test]
1208    fn literal_booleans_and_null() {
1209        assert!(matches!(
1210            parse("TRUE"),
1211            Expr::Literal {
1212                value: Value::Boolean(true),
1213                ..
1214            }
1215        ));
1216        assert!(matches!(
1217            parse("FALSE"),
1218            Expr::Literal {
1219                value: Value::Boolean(false),
1220                ..
1221            }
1222        ));
1223        assert!(matches!(
1224            parse("NULL"),
1225            Expr::Literal {
1226                value: Value::Null,
1227                ..
1228            }
1229        ));
1230    }
1231
1232    #[test]
1233    fn bare_column() {
1234        let e = parse("user_id");
1235        match e {
1236            Expr::Column {
1237                field: FieldRef::TableColumn { column, .. },
1238                ..
1239            } => {
1240                assert_eq!(column, "user_id");
1241            }
1242            other => panic!("expected column, got {other:?}"),
1243        }
1244    }
1245
1246    #[test]
1247    fn arithmetic_precedence_mul_over_add() {
1248        // a + b * c  →  Add(a, Mul(b, c))
1249        let e = parse("a + b * c");
1250        let Expr::BinaryOp {
1251            op: BinOp::Add,
1252            rhs,
1253            ..
1254        } = e
1255        else {
1256            panic!("root must be Add");
1257        };
1258        let Expr::BinaryOp { op: BinOp::Mul, .. } = *rhs else {
1259            panic!("rhs must be Mul");
1260        };
1261    }
1262
1263    #[test]
1264    fn arithmetic_left_associativity() {
1265        // a - b - c  →  Sub(Sub(a, b), c)
1266        let e = parse("a - b - c");
1267        let Expr::BinaryOp {
1268            op: BinOp::Sub,
1269            lhs,
1270            ..
1271        } = e
1272        else {
1273            panic!("root must be Sub");
1274        };
1275        let Expr::BinaryOp { op: BinOp::Sub, .. } = *lhs else {
1276            panic!("lhs must be Sub (left-assoc)");
1277        };
1278    }
1279
1280    #[test]
1281    fn parenthesised_override() {
1282        // (a + b) * c  →  Mul(Add(a, b), c)
1283        let e = parse("(a + b) * c");
1284        let Expr::BinaryOp {
1285            op: BinOp::Mul,
1286            lhs,
1287            ..
1288        } = e
1289        else {
1290            panic!("root must be Mul");
1291        };
1292        let Expr::BinaryOp { op: BinOp::Add, .. } = *lhs else {
1293            panic!("lhs must be Add");
1294        };
1295    }
1296
1297    #[test]
1298    fn comparison_binds_weaker_than_arith() {
1299        // a + 1 = b - 2
1300        //   →  Eq(Add(a, 1), Sub(b, 2))
1301        let e = parse("a + 1 = b - 2");
1302        let Expr::BinaryOp {
1303            op: BinOp::Eq,
1304            lhs,
1305            rhs,
1306            ..
1307        } = e
1308        else {
1309            panic!("root must be Eq");
1310        };
1311        assert!(matches!(*lhs, Expr::BinaryOp { op: BinOp::Add, .. }));
1312        assert!(matches!(*rhs, Expr::BinaryOp { op: BinOp::Sub, .. }));
1313    }
1314
1315    #[test]
1316    fn and_binds_tighter_than_or() {
1317        // a OR b AND c  →  Or(a, And(b, c))
1318        let e = parse("a OR b AND c");
1319        let Expr::BinaryOp {
1320            op: BinOp::Or, rhs, ..
1321        } = e
1322        else {
1323            panic!("root must be Or");
1324        };
1325        assert!(matches!(*rhs, Expr::BinaryOp { op: BinOp::And, .. }));
1326    }
1327
1328    #[test]
1329    fn unary_negation() {
1330        let e = parse("-a");
1331        let Expr::UnaryOp {
1332            op: UnaryOp::Neg, ..
1333        } = e
1334        else {
1335            panic!("expected unary Neg");
1336        };
1337    }
1338
1339    #[test]
1340    fn unary_not() {
1341        let e = parse("NOT a");
1342        let Expr::UnaryOp {
1343            op: UnaryOp::Not, ..
1344        } = e
1345        else {
1346            panic!("expected unary Not");
1347        };
1348    }
1349
1350    #[test]
1351    fn concat_operator() {
1352        let e = parse("'hello' || name");
1353        let Expr::BinaryOp {
1354            op: BinOp::Concat, ..
1355        } = e
1356        else {
1357            panic!("expected Concat");
1358        };
1359    }
1360
1361    #[test]
1362    fn cast_expr() {
1363        let e = parse("CAST(age AS TEXT)");
1364        let Expr::Cast { target, .. } = e else {
1365            panic!("expected Cast");
1366        };
1367        assert_eq!(target, DataType::Text);
1368    }
1369
1370    #[test]
1371    fn case_expr() {
1372        let e = parse("CASE WHEN a = 1 THEN 'one' WHEN a = 2 THEN 'two' ELSE 'other' END");
1373        let Expr::Case {
1374            branches, else_, ..
1375        } = e
1376        else {
1377            panic!("expected Case");
1378        };
1379        assert_eq!(branches.len(), 2);
1380        assert!(else_.is_some());
1381    }
1382
1383    #[test]
1384    fn is_null_postfix() {
1385        let e = parse("name IS NULL");
1386        assert!(matches!(e, Expr::IsNull { negated: false, .. }));
1387    }
1388
1389    #[test]
1390    fn is_not_null_postfix() {
1391        let e = parse("name IS NOT NULL");
1392        assert!(matches!(e, Expr::IsNull { negated: true, .. }));
1393    }
1394
1395    #[test]
1396    fn between_with_columns() {
1397        let e = parse("temp BETWEEN min_t AND max_t");
1398        let Expr::Between {
1399            target,
1400            low,
1401            high,
1402            negated,
1403            ..
1404        } = e
1405        else {
1406            panic!("expected Between");
1407        };
1408        assert!(!negated);
1409        assert!(matches!(*target, Expr::Column { .. }));
1410        assert!(matches!(*low, Expr::Column { .. }));
1411        assert!(matches!(*high, Expr::Column { .. }));
1412    }
1413
1414    #[test]
1415    fn not_between_negates() {
1416        let e = parse("temp NOT BETWEEN 0 AND 100");
1417        let Expr::Between { negated: true, .. } = e else {
1418            panic!("expected negated Between");
1419        };
1420    }
1421
1422    #[test]
1423    fn in_list_literal() {
1424        let e = parse("status IN (1, 2, 3)");
1425        let Expr::InList {
1426            values, negated, ..
1427        } = e
1428        else {
1429            panic!("expected InList");
1430        };
1431        assert!(!negated);
1432        assert_eq!(values.len(), 3);
1433    }
1434
1435    #[test]
1436    fn not_in_list() {
1437        let e = parse("status NOT IN (1, 2)");
1438        let Expr::InList { negated: true, .. } = e else {
1439            panic!("expected negated InList");
1440        };
1441    }
1442
1443    #[test]
1444    fn function_call_with_args() {
1445        let e = parse("UPPER(name)");
1446        let Expr::FunctionCall { name, args, .. } = e else {
1447            panic!("expected FunctionCall");
1448        };
1449        assert_eq!(name, "UPPER");
1450        assert_eq!(args.len(), 1);
1451    }
1452
1453    #[test]
1454    fn nested_function_call() {
1455        let e = parse("COALESCE(a, UPPER(b))");
1456        let Expr::FunctionCall { name, args, .. } = e else {
1457            panic!("expected FunctionCall");
1458        };
1459        assert_eq!(name, "COALESCE");
1460        assert_eq!(args.len(), 2);
1461        assert!(matches!(&args[1], Expr::FunctionCall { .. }));
1462    }
1463
1464    #[test]
1465    fn duration_literal_parses_as_text() {
1466        let e = parse("time_bucket(5m)");
1467        let Expr::FunctionCall { name, args, .. } = e else {
1468            panic!("expected FunctionCall, got {e:?}");
1469        };
1470        assert_eq!(name.to_uppercase(), "TIME_BUCKET");
1471        assert_eq!(args.len(), 1);
1472        assert!(
1473            matches!(&args[0], Expr::Literal { value: Value::Text(s), .. } if s.as_ref() == "5m"),
1474            "expected Text(\"5m\"), got {:?}",
1475            args[0]
1476        );
1477    }
1478
1479    #[test]
1480    fn placeholder_dollar_one() {
1481        let e = parse("$1");
1482        match e {
1483            Expr::Parameter { index: 0, .. } => {}
1484            other => panic!("expected Parameter(0), got {other:?}"),
1485        }
1486    }
1487
1488    #[test]
1489    fn placeholder_dollar_n() {
1490        let e = parse("$7");
1491        match e {
1492            Expr::Parameter { index: 6, .. } => {}
1493            other => panic!("expected Parameter(6), got {other:?}"),
1494        }
1495    }
1496
1497    #[test]
1498    fn placeholder_in_string_literal_is_text() {
1499        // `$1` inside a string literal must NOT parse as a placeholder.
1500        let e = parse("'$1'");
1501        match e {
1502            Expr::Literal {
1503                value: Value::Text(s),
1504                ..
1505            } if s.as_ref() == "$1" => {}
1506            other => panic!("expected text literal '$1', got {other:?}"),
1507        }
1508    }
1509
1510    #[test]
1511    fn placeholder_in_comparison() {
1512        // SELECT-WHERE shape: `id = $1`
1513        let e = parse("id = $1");
1514        let Expr::BinaryOp {
1515            op: BinOp::Eq, rhs, ..
1516        } = e
1517        else {
1518            panic!("root must be Eq");
1519        };
1520        assert!(matches!(*rhs, Expr::Parameter { index: 0, .. }));
1521    }
1522
1523    #[test]
1524    fn placeholder_zero_rejected() {
1525        let mut parser = Parser::new("$0").expect("lexer");
1526        let err = parser.parse_expr().unwrap_err();
1527        assert!(err.to_string().contains("placeholder"));
1528    }
1529
1530    #[test]
1531    fn placeholder_question_single() {
1532        // Lone `?` numbered as parameter 1 (index 0).
1533        let e = parse("?");
1534        match e {
1535            Expr::Parameter { index: 0, .. } => {}
1536            other => panic!("expected Parameter(0), got {other:?}"),
1537        }
1538    }
1539
1540    #[test]
1541    fn placeholder_question_numbered() {
1542        let e = parse("?7");
1543        match e {
1544            Expr::Parameter { index: 6, .. } => {}
1545            other => panic!("expected Parameter(6), got {other:?}"),
1546        }
1547    }
1548
1549    #[test]
1550    fn placeholder_question_numbered_zero_rejected() {
1551        let mut parser = Parser::new("?0").expect("lexer");
1552        let err = parser.parse_expr().unwrap_err();
1553        assert!(err.to_string().contains("placeholder"));
1554    }
1555
1556    #[test]
1557    fn placeholder_question_left_to_right() {
1558        // `id = ? AND name = ?` → params 0 and 1
1559        let e = parse("id = ? AND name = ?");
1560        let Expr::BinaryOp {
1561            op: BinOp::And,
1562            lhs,
1563            rhs,
1564            ..
1565        } = e
1566        else {
1567            panic!("root must be And");
1568        };
1569        let Expr::BinaryOp {
1570            op: BinOp::Eq,
1571            rhs: r1,
1572            ..
1573        } = *lhs
1574        else {
1575            panic!("lhs must be Eq");
1576        };
1577        assert!(matches!(*r1, Expr::Parameter { index: 0, .. }));
1578        let Expr::BinaryOp {
1579            op: BinOp::Eq,
1580            rhs: r2,
1581            ..
1582        } = *rhs
1583        else {
1584            panic!("rhs must be Eq");
1585        };
1586        assert!(matches!(*r2, Expr::Parameter { index: 1, .. }));
1587    }
1588
1589    #[test]
1590    fn placeholder_question_in_string_literal_is_text() {
1591        let e = parse("'?'");
1592        match e {
1593            Expr::Literal {
1594                value: Value::Text(s),
1595                ..
1596            } if s.as_ref() == "?" => {}
1597            other => panic!("expected text literal '?', got {other:?}"),
1598        }
1599    }
1600
1601    #[test]
1602    fn placeholder_mixing_question_then_dollar_rejected() {
1603        let mut parser = Parser::new("id = ? AND x = $2").expect("lexer");
1604        let err = parser.parse_expr().err().expect("should fail");
1605        assert!(
1606            err.to_string().contains("mix"),
1607            "expected mixing error, got: {err}"
1608        );
1609    }
1610
1611    #[test]
1612    fn placeholder_mixing_dollar_then_question_rejected() {
1613        let mut parser = Parser::new("id = $1 AND x = ?").expect("lexer");
1614        let err = parser.parse_expr().err().expect("should fail");
1615        assert!(
1616            err.to_string().contains("mix"),
1617            "expected mixing error, got: {err}"
1618        );
1619    }
1620
1621    #[test]
1622    fn placeholder_question_in_comment_ignored() {
1623        // `?` inside an SQL line comment must not bump the counter.
1624        // The expression after the comment is the only param.
1625        let mut parser = Parser::new("-- ? ignored\n  ?").expect("lexer");
1626        let e = parser.parse_expr().expect("parse_expr");
1627        match e {
1628            Expr::Parameter { index: 0, .. } => {}
1629            other => panic!("expected Parameter(0), got {other:?}"),
1630        }
1631    }
1632
1633    #[test]
1634    fn span_tracks_token_range() {
1635        // A literal's span must cover the exact tokens consumed.
1636        let mut parser = Parser::new("123 + 456").expect("lexer");
1637        let e = parser.parse_expr().expect("parse_expr");
1638        let span = e.span();
1639        assert!(!span.is_synthetic(), "root span must be real");
1640        assert!(span.start.offset < span.end.offset);
1641    }
1642
1643    // ====================================================================
1644    // Window OVER clause — issue #589 slice 7a
1645    // ====================================================================
1646
1647    fn try_parse(input: &str) -> Result<Expr, ParseError> {
1648        let mut parser = Parser::new(input).expect("lexer init");
1649        parser.parse_expr()
1650    }
1651
1652    #[test]
1653    fn window_lag_partition_and_order() {
1654        let e = parse("LAG(ts) OVER (PARTITION BY user_id ORDER BY ts)");
1655        let Expr::WindowFunctionCall {
1656            name, args, window, ..
1657        } = e
1658        else {
1659            panic!("expected WindowFunctionCall");
1660        };
1661        assert_eq!(name.to_uppercase(), "LAG");
1662        assert_eq!(args.len(), 1);
1663        assert_eq!(window.partition_by.len(), 1);
1664        assert_eq!(window.order_by.len(), 1);
1665        assert!(window.order_by[0].ascending);
1666        assert!(window.frame.is_none());
1667    }
1668
1669    #[test]
1670    fn window_row_number_empty_over() {
1671        let e = parse("ROW_NUMBER() OVER ()");
1672        let Expr::WindowFunctionCall {
1673            name, args, window, ..
1674        } = e
1675        else {
1676            panic!("expected WindowFunctionCall");
1677        };
1678        assert_eq!(name.to_uppercase(), "ROW_NUMBER");
1679        assert!(args.is_empty());
1680        assert!(window.partition_by.is_empty());
1681        assert!(window.order_by.is_empty());
1682        assert!(window.frame.is_none());
1683    }
1684
1685    #[test]
1686    fn window_sum_with_frame_rows_between() {
1687        let e = parse(
1688            "SUM(amount) OVER (PARTITION BY user_id ORDER BY ts \
1689             ROWS BETWEEN 2 PRECEDING AND CURRENT ROW)",
1690        );
1691        let Expr::WindowFunctionCall { name, window, .. } = e else {
1692            panic!("expected WindowFunctionCall");
1693        };
1694        assert_eq!(name.to_uppercase(), "SUM");
1695        let frame = window.frame.expect("frame present");
1696        assert!(matches!(
1697            frame.unit,
1698            crate::storage::query::ast::WindowFrameUnit::Rows
1699        ));
1700        assert!(matches!(
1701            frame.start,
1702            crate::storage::query::ast::WindowFrameBound::Preceding(_)
1703        ));
1704        assert!(matches!(
1705            frame.end,
1706            Some(crate::storage::query::ast::WindowFrameBound::CurrentRow)
1707        ));
1708    }
1709
1710    #[test]
1711    fn window_rank_order_desc_multiple_keys() {
1712        let e = parse("RANK() OVER (ORDER BY score DESC, ts)");
1713        let Expr::WindowFunctionCall { window, .. } = e else {
1714            panic!("expected WindowFunctionCall");
1715        };
1716        assert_eq!(window.order_by.len(), 2);
1717        assert!(!window.order_by[0].ascending);
1718        assert!(window.order_by[1].ascending);
1719    }
1720
1721    #[test]
1722    fn window_unbounded_preceding_following_frame() {
1723        let e = parse(
1724            "AVG(x) OVER (ORDER BY t \
1725             RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)",
1726        );
1727        let Expr::WindowFunctionCall { window, .. } = e else {
1728            panic!("expected WindowFunctionCall");
1729        };
1730        let frame = window.frame.expect("frame present");
1731        assert!(matches!(
1732            frame.unit,
1733            crate::storage::query::ast::WindowFrameUnit::Range
1734        ));
1735        assert!(matches!(
1736            frame.start,
1737            crate::storage::query::ast::WindowFrameBound::UnboundedPreceding
1738        ));
1739        assert!(matches!(
1740            frame.end,
1741            Some(crate::storage::query::ast::WindowFrameBound::UnboundedFollowing)
1742        ));
1743    }
1744
1745    #[test]
1746    fn window_rejects_non_window_function() {
1747        // UPPER is a scalar function, not eligible for OVER.
1748        let err = try_parse("UPPER(name) OVER (PARTITION BY id)")
1749            .err()
1750            .expect("should reject scalar OVER");
1751        let msg = err.to_string();
1752        assert!(
1753            msg.contains("UPPER") || msg.contains("upper"),
1754            "error should mention function name, got: {msg}"
1755        );
1756        assert!(msg.to_ascii_uppercase().contains("OVER") || msg.contains("window"));
1757    }
1758
1759    #[test]
1760    fn window_rejects_missing_open_paren() {
1761        let err = try_parse("LAG(ts) OVER PARTITION BY user_id")
1762            .err()
1763            .expect("should reject");
1764        let msg = err.to_string();
1765        assert!(
1766            msg.contains("(") || msg.to_ascii_uppercase().contains("EXPECTED"),
1767            "got: {msg}"
1768        );
1769    }
1770
1771    #[test]
1772    fn window_rejects_invalid_frame_syntax() {
1773        // CURRENT without ROW is malformed.
1774        let err = try_parse("LAG(ts) OVER (ORDER BY ts ROWS CURRENT)")
1775            .err()
1776            .expect("should reject");
1777        let msg = err.to_string();
1778        assert!(
1779            !msg.is_empty(),
1780            "expected non-empty error for malformed frame"
1781        );
1782    }
1783
1784    #[test]
1785    fn window_first_value_with_partition_only() {
1786        let e = parse("FIRST_VALUE(price) OVER (PARTITION BY symbol)");
1787        let Expr::WindowFunctionCall {
1788            name, window, args, ..
1789        } = e
1790        else {
1791            panic!("expected WindowFunctionCall");
1792        };
1793        assert_eq!(name.to_uppercase(), "FIRST_VALUE");
1794        assert_eq!(args.len(), 1);
1795        assert_eq!(window.partition_by.len(), 1);
1796        assert!(window.order_by.is_empty());
1797    }
1798}