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        let mut args = Vec::new();
610        if !self.check(&Token::RParen) {
611            loop {
612                args.push(self.parse_expr_prec(0)?);
613                if !self.consume(&Token::Comma)? {
614                    break;
615                }
616            }
617        }
618        self.expect(Token::RParen)?;
619        let end = self.position();
620        Ok(Expr::FunctionCall {
621            name: function_name,
622            args,
623            span: Span::new(start, end),
624        })
625    }
626
627    /// Wrap a freshly-parsed `Expr::FunctionCall` in
628    /// `Expr::WindowFunctionCall` by consuming the trailing `OVER (...)`
629    /// clause. The caller has already confirmed the next token is
630    /// `OVER`. Rejects:
631    /// - CAST(...) OVER (...) and other non-FunctionCall shapes.
632    /// - Function names that are neither window-only nor aggregates.
633    fn lift_to_window_call(
634        &mut self,
635        start: crate::storage::query::lexer::Position,
636        call: Expr,
637    ) -> Result<Expr, ParseError> {
638        let (name, args) = match call {
639            Expr::FunctionCall { name, args, .. } => (name, args),
640            other => {
641                return Err(ParseError::new(
642                    format!(
643                        "OVER may only follow a function call, got {:?}",
644                        std::mem::discriminant(&other)
645                    ),
646                    self.position(),
647                ));
648            }
649        };
650        if !is_window_eligible_function(&name) {
651            return Err(ParseError::new(
652                format!(
653                    "function `{}` cannot be used with an OVER clause; \
654                     expected a window function (LAG, LEAD, ROW_NUMBER, \
655                     RANK, DENSE_RANK) or an aggregate",
656                    name.to_uppercase()
657                ),
658                self.position(),
659            ));
660        }
661        let window = self.parse_over_clause()?;
662        let end = self.position();
663        Ok(Expr::WindowFunctionCall {
664            name,
665            args,
666            window,
667            span: Span::new(start, end),
668        })
669    }
670
671    /// Parse the `OVER ( [PARTITION BY ...] [ORDER BY ...] [frame] )`
672    /// clause. The leading `OVER` keyword is consumed here.
673    fn parse_over_clause(&mut self) -> Result<crate::storage::query::ast::WindowSpec, ParseError> {
674        self.expect(Token::Over)?;
675        self.expect(Token::LParen)?;
676
677        let mut spec = crate::storage::query::ast::WindowSpec::default();
678
679        if self.consume(&Token::Partition)? {
680            self.expect(Token::By)?;
681            loop {
682                spec.partition_by.push(self.parse_expr_prec(0)?);
683                if !self.consume(&Token::Comma)? {
684                    break;
685                }
686            }
687        }
688
689        if self.consume(&Token::Order)? {
690            self.expect(Token::By)?;
691            loop {
692                let expr = self.parse_expr_prec(0)?;
693                let ascending = if self.consume(&Token::Desc)? {
694                    false
695                } else {
696                    self.consume(&Token::Asc)?;
697                    true
698                };
699                // NULLS FIRST / LAST defaults mirror PG: nulls last for
700                // ASC, nulls first for DESC. Explicit clause overrides.
701                let mut nulls_first = !ascending;
702                if self.consume(&Token::Nulls)? {
703                    if self.consume(&Token::First)? {
704                        nulls_first = true;
705                    } else if self.consume(&Token::Last)? {
706                        nulls_first = false;
707                    } else {
708                        return Err(ParseError::new(
709                            "expected FIRST or LAST after NULLS".to_string(),
710                            self.position(),
711                        ));
712                    }
713                }
714                spec.order_by
715                    .push(crate::storage::query::ast::WindowOrderItem {
716                        expr,
717                        ascending,
718                        nulls_first,
719                    });
720                if !self.consume(&Token::Comma)? {
721                    break;
722                }
723            }
724        }
725
726        if matches!(self.peek(), Token::Rows | Token::Range) {
727            spec.frame = Some(self.parse_window_frame()?);
728        }
729
730        self.expect(Token::RParen)?;
731        Ok(spec)
732    }
733
734    fn parse_window_frame(
735        &mut self,
736    ) -> Result<crate::storage::query::ast::WindowFrame, ParseError> {
737        let unit = if self.consume(&Token::Rows)? {
738            crate::storage::query::ast::WindowFrameUnit::Rows
739        } else if self.consume(&Token::Range)? {
740            crate::storage::query::ast::WindowFrameUnit::Range
741        } else {
742            return Err(ParseError::new(
743                "expected ROWS or RANGE in window frame".to_string(),
744                self.position(),
745            ));
746        };
747
748        if self.consume(&Token::Between)? {
749            let start = self.parse_window_frame_bound()?;
750            self.expect(Token::And)?;
751            let end = self.parse_window_frame_bound()?;
752            Ok(crate::storage::query::ast::WindowFrame {
753                unit,
754                start,
755                end: Some(end),
756            })
757        } else {
758            let start = self.parse_window_frame_bound()?;
759            Ok(crate::storage::query::ast::WindowFrame {
760                unit,
761                start,
762                end: None,
763            })
764        }
765    }
766
767    fn parse_window_frame_bound(
768        &mut self,
769    ) -> Result<crate::storage::query::ast::WindowFrameBound, ParseError> {
770        use crate::storage::query::ast::WindowFrameBound;
771        if self.consume(&Token::Unbounded)? {
772            if self.consume(&Token::Preceding)? {
773                return Ok(WindowFrameBound::UnboundedPreceding);
774            }
775            if self.consume(&Token::Following)? {
776                return Ok(WindowFrameBound::UnboundedFollowing);
777            }
778            return Err(ParseError::new(
779                "expected PRECEDING or FOLLOWING after UNBOUNDED".to_string(),
780                self.position(),
781            ));
782        }
783        if self.consume(&Token::Current)? {
784            self.expect(Token::Row)?;
785            return Ok(WindowFrameBound::CurrentRow);
786        }
787        // Numeric / expression offset: `N PRECEDING` / `N FOLLOWING`.
788        let offset = self.parse_expr_prec(0)?;
789        if self.consume(&Token::Preceding)? {
790            return Ok(WindowFrameBound::Preceding(Box::new(offset)));
791        }
792        if self.consume(&Token::Following)? {
793            return Ok(WindowFrameBound::Following(Box::new(offset)));
794        }
795        Err(ParseError::new(
796            "expected PRECEDING or FOLLOWING after frame offset".to_string(),
797            self.position(),
798        ))
799    }
800
801    /// Parse `CASE WHEN cond THEN val [WHEN …] [ELSE val] END`.
802    /// Assumes the caller has already peeked `CASE`.
803    fn parse_case_expr(
804        &mut self,
805        start: crate::storage::query::lexer::Position,
806    ) -> Result<Expr, ParseError> {
807        self.advance()?; // consume CASE
808        let mut branches: Vec<(Expr, Expr)> = Vec::new();
809        loop {
810            if !self.consume_ident_ci("WHEN")? {
811                break;
812            }
813            let cond = self.parse_expr_prec(0)?;
814            if !self.consume_ident_ci("THEN")? {
815                return Err(ParseError::new(
816                    "expected THEN after CASE WHEN condition".to_string(),
817                    self.position(),
818                ));
819            }
820            let then_val = self.parse_expr_prec(0)?;
821            branches.push((cond, then_val));
822        }
823        if branches.is_empty() {
824            return Err(ParseError::new(
825                "CASE must have at least one WHEN branch".to_string(),
826                self.position(),
827            ));
828        }
829        let else_ = if self.consume_ident_ci("ELSE")? {
830            Some(Box::new(self.parse_expr_prec(0)?))
831        } else {
832            None
833        };
834        if !self.consume_ident_ci("END")? {
835            return Err(ParseError::new(
836                "expected END to close CASE expression".to_string(),
837                self.position(),
838            ));
839        }
840        let end = self.position();
841        Ok(Expr::Case {
842            branches,
843            else_,
844            span: Span::new(start, end),
845        })
846    }
847
848    fn parse_trim_expr_args(&mut self) -> Result<(String, Vec<Expr>), ParseError> {
849        let mut function_name = "TRIM".to_string();
850
851        if self.consume_ident_ci("LEADING")? {
852            function_name = "LTRIM".to_string();
853        } else if self.consume_ident_ci("TRAILING")? {
854            function_name = "RTRIM".to_string();
855        } else if self.consume_ident_ci("BOTH")? {
856            function_name = "TRIM".to_string();
857        }
858
859        if self.consume(&Token::From)? {
860            let source = self.parse_expr_prec(0)?;
861            return Ok((function_name, vec![source]));
862        }
863
864        let first = self.parse_expr_prec(0)?;
865
866        if self.consume(&Token::Comma)? {
867            let second = self.parse_expr_prec(0)?;
868            return Ok((function_name, vec![first, second]));
869        }
870
871        if self.consume(&Token::From)? {
872            let source = self.parse_expr_prec(0)?;
873            return Ok((function_name, vec![source, first]));
874        }
875
876        Ok((function_name, vec![first]))
877    }
878
879    /// PostgreSQL-style `POSITION(substr IN string)` or plain
880    /// `POSITION(substr, string)` lowered to the ordinary two-argument
881    /// function form.
882    fn parse_position_expr_args(&mut self) -> Result<Vec<Expr>, ParseError> {
883        // `IN` is also a postfix operator in the main expression grammar, so
884        // parse the first operand above postfix-IN precedence and then consume
885        // the function's `IN` keyword explicitly.
886        let needle = self.parse_expr_prec(35)?;
887        if !self.consume(&Token::Comma)? {
888            self.expect(Token::In)?;
889        }
890        let haystack = self.parse_expr_prec(0)?;
891        Ok(vec![needle, haystack])
892    }
893
894    /// PostgreSQL-style `SUBSTRING` syntax:
895    /// - `SUBSTRING(expr FROM start [FOR count])`
896    /// - `SUBSTRING(expr FOR count [FROM start])`
897    /// - plain function-call form `SUBSTRING(expr, start[, count])`
898    ///
899    /// The SQL-syntax variants are desugared to the comma-arg form so the
900    /// rest of the stack sees the same `Expr::FunctionCall` shape.
901    fn parse_substring_expr_args(&mut self) -> Result<Vec<Expr>, ParseError> {
902        let source = self.parse_expr_prec(0)?;
903
904        if self.consume(&Token::Comma)? {
905            let mut args = vec![source];
906            loop {
907                args.push(self.parse_expr_prec(0)?);
908                if !self.consume(&Token::Comma)? {
909                    break;
910                }
911            }
912            return Ok(args);
913        }
914
915        if self.consume(&Token::From)? {
916            let start = self.parse_expr_prec(0)?;
917            if self.consume(&Token::For)? {
918                let count = self.parse_expr_prec(0)?;
919                return Ok(vec![source, start, count]);
920            }
921            return Ok(vec![source, start]);
922        }
923
924        if self.consume(&Token::For)? {
925            let count = self.parse_expr_prec(0)?;
926            if self.consume(&Token::From)? {
927                let start = self.parse_expr_prec(0)?;
928                return Ok(vec![source, start, count]);
929            }
930            return Ok(vec![source, Expr::lit(Value::Integer(1)), count]);
931        }
932
933        Ok(vec![source])
934    }
935
936    /// Try to consume a postfix operator on top of the already-parsed
937    /// `left` expression: `IS [NOT] NULL`, `[NOT] BETWEEN … AND …`,
938    /// `[NOT] IN (…)`. Returns `Ok(None)` if no postfix follows.
939    ///
940    /// NOT at this position is unambiguous — prefix `NOT` is always
941    /// consumed at `parse_expr_unary` level before reaching postfix.
942    /// So seeing `NOT` here means the user wrote `x NOT BETWEEN …`
943    /// or `x NOT IN …`; we consume it eagerly and require BETWEEN
944    /// or IN to follow.
945    fn try_parse_postfix(&mut self, left: &Expr) -> Result<Option<Expr>, ParseError> {
946        let start = self.span_start_of(left);
947
948        // IS [NOT] NULL
949        if self.consume(&Token::Is)? {
950            let negated = self.consume(&Token::Not)?;
951            self.expect(Token::Null)?;
952            let end = self.position();
953            return Ok(Some(Expr::IsNull {
954                operand: Box::new(left.clone()),
955                negated,
956                span: Span::new(start, end),
957            }));
958        }
959
960        // Detect NOT BETWEEN / NOT IN. NOT is consumed eagerly — we
961        // don't have two-token lookahead and the grammar guarantees
962        // no other valid postfix starts with NOT.
963        let negated = if matches!(self.peek(), Token::Not) {
964            self.advance()?;
965            if !matches!(self.peek(), Token::Between | Token::In) {
966                return Err(ParseError::new(
967                    "expected BETWEEN or IN after postfix NOT".to_string(),
968                    self.position(),
969                ));
970            }
971            true
972        } else {
973            false
974        };
975
976        // BETWEEN low AND high
977        if self.consume(&Token::Between)? {
978            let low = self.parse_expr_prec(34)?;
979            self.expect(Token::And)?;
980            let high = self.parse_expr_prec(34)?;
981            let end = self.position();
982            return Ok(Some(Expr::Between {
983                target: Box::new(left.clone()),
984                low: Box::new(low),
985                high: Box::new(high),
986                negated,
987                span: Span::new(start, end),
988            }));
989        }
990
991        // IN (v1, v2, …)
992        if self.consume(&Token::In)? {
993            self.expect(Token::LParen)?;
994            let mut values = Vec::new();
995            if self.check(&Token::Select) {
996                let query = self.parse_select_query()?;
997                values.push(Expr::Subquery {
998                    query: ExprSubquery {
999                        query: Box::new(query),
1000                    },
1001                    span: Span::new(self.span_start_of(left), self.position()),
1002                });
1003            } else if !self.check(&Token::RParen) {
1004                loop {
1005                    values.push(self.parse_expr_prec(0)?);
1006                    if !self.consume(&Token::Comma)? {
1007                        break;
1008                    }
1009                }
1010            }
1011            self.expect(Token::RParen)?;
1012            let end = self.position();
1013            return Ok(Some(Expr::InList {
1014                target: Box::new(left.clone()),
1015                values,
1016                negated,
1017                span: Span::new(start, end),
1018            }));
1019        }
1020
1021        if negated {
1022            // Unreachable because the early-return above already
1023            // validated NOT is followed by BETWEEN or IN. Guarded
1024            // to keep callers loud if the grammar grows later.
1025            return Err(ParseError::new(
1026                "internal: NOT consumed without BETWEEN/IN follow".to_string(),
1027                self.position(),
1028            ));
1029        }
1030        Ok(None)
1031    }
1032
1033    /// Peek the current token and translate it into a `BinOp` plus
1034    /// its precedence. Returns `None` if the token is not a recognised
1035    /// infix operator — the caller then tries postfix handling.
1036    fn peek_binop(&self) -> Option<(BinOp, u8)> {
1037        let op = match self.peek() {
1038            Token::Or => BinOp::Or,
1039            Token::And => BinOp::And,
1040            Token::Eq => BinOp::Eq,
1041            Token::Ne => BinOp::Ne,
1042            Token::Lt => BinOp::Lt,
1043            Token::Le => BinOp::Le,
1044            Token::Gt => BinOp::Gt,
1045            Token::Ge => BinOp::Ge,
1046            Token::DoublePipe => BinOp::Concat,
1047            Token::Plus => BinOp::Add,
1048            Token::Dash => BinOp::Sub,
1049            Token::Star => BinOp::Mul,
1050            Token::Slash => BinOp::Div,
1051            Token::Percent => BinOp::Mod,
1052            _ => return None,
1053        };
1054        Some((op, op.precedence()))
1055    }
1056
1057    /// Return the start position of an expression's span. Handles the
1058    /// synthetic case by falling back to the current parser cursor,
1059    /// which is good enough for the Pratt climb since the caller just
1060    /// parsed the atom.
1061    fn span_start_of(&self, expr: &Expr) -> crate::storage::query::lexer::Position {
1062        let s = expr.span();
1063        if s.is_synthetic() {
1064            self.position()
1065        } else {
1066            s.start
1067        }
1068    }
1069
1070    /// Return the end position of an expression's span — same
1071    /// synthetic fallback as `span_start_of`.
1072    fn span_end_of(&self, expr: &Expr) -> crate::storage::query::lexer::Position {
1073        let s = expr.span();
1074        if s.is_synthetic() {
1075            self.position()
1076        } else {
1077            s.end
1078        }
1079    }
1080}
1081
1082// Avoid `unused` lints in partial-migration builds where the analyzer
1083// still does not consume every expression shape directly.
1084#[allow(dead_code)]
1085fn _expr_module_used(_: Expr) {}
1086
1087#[cfg(test)]
1088mod tests {
1089    use super::*;
1090    use crate::storage::query::ast::FieldRef;
1091
1092    fn parse(input: &str) -> Expr {
1093        let mut parser = Parser::new(input).expect("lexer init");
1094        let expr = parser.parse_expr().expect("parse_expr");
1095        expr
1096    }
1097
1098    #[test]
1099    fn literal_integer() {
1100        let e = parse("42");
1101        match e {
1102            Expr::Literal {
1103                value: Value::Integer(42),
1104                ..
1105            } => {}
1106            other => panic!("expected Integer(42), got {other:?}"),
1107        }
1108    }
1109
1110    #[test]
1111    fn literal_float() {
1112        let e = parse("3.14");
1113        match e {
1114            Expr::Literal {
1115                value: Value::Float(f),
1116                ..
1117            } => assert!((f - 3.14).abs() < 1e-9),
1118            other => panic!("expected float literal, got {other:?}"),
1119        }
1120    }
1121
1122    #[test]
1123    fn literal_string() {
1124        let e = parse("'hello'");
1125        match e {
1126            Expr::Literal {
1127                value: Value::Text(ref s),
1128                ..
1129            } if s.as_ref() == "hello" => {}
1130            other => panic!("expected Text(hello), got {other:?}"),
1131        }
1132    }
1133
1134    #[test]
1135    fn literal_booleans_and_null() {
1136        assert!(matches!(
1137            parse("TRUE"),
1138            Expr::Literal {
1139                value: Value::Boolean(true),
1140                ..
1141            }
1142        ));
1143        assert!(matches!(
1144            parse("FALSE"),
1145            Expr::Literal {
1146                value: Value::Boolean(false),
1147                ..
1148            }
1149        ));
1150        assert!(matches!(
1151            parse("NULL"),
1152            Expr::Literal {
1153                value: Value::Null,
1154                ..
1155            }
1156        ));
1157    }
1158
1159    #[test]
1160    fn bare_column() {
1161        let e = parse("user_id");
1162        match e {
1163            Expr::Column {
1164                field: FieldRef::TableColumn { column, .. },
1165                ..
1166            } => {
1167                assert_eq!(column, "user_id");
1168            }
1169            other => panic!("expected column, got {other:?}"),
1170        }
1171    }
1172
1173    #[test]
1174    fn arithmetic_precedence_mul_over_add() {
1175        // a + b * c  →  Add(a, Mul(b, c))
1176        let e = parse("a + b * c");
1177        let Expr::BinaryOp {
1178            op: BinOp::Add,
1179            rhs,
1180            ..
1181        } = e
1182        else {
1183            panic!("root must be Add");
1184        };
1185        let Expr::BinaryOp { op: BinOp::Mul, .. } = *rhs else {
1186            panic!("rhs must be Mul");
1187        };
1188    }
1189
1190    #[test]
1191    fn arithmetic_left_associativity() {
1192        // a - b - c  →  Sub(Sub(a, b), c)
1193        let e = parse("a - b - c");
1194        let Expr::BinaryOp {
1195            op: BinOp::Sub,
1196            lhs,
1197            ..
1198        } = e
1199        else {
1200            panic!("root must be Sub");
1201        };
1202        let Expr::BinaryOp { op: BinOp::Sub, .. } = *lhs else {
1203            panic!("lhs must be Sub (left-assoc)");
1204        };
1205    }
1206
1207    #[test]
1208    fn parenthesised_override() {
1209        // (a + b) * c  →  Mul(Add(a, b), c)
1210        let e = parse("(a + b) * c");
1211        let Expr::BinaryOp {
1212            op: BinOp::Mul,
1213            lhs,
1214            ..
1215        } = e
1216        else {
1217            panic!("root must be Mul");
1218        };
1219        let Expr::BinaryOp { op: BinOp::Add, .. } = *lhs else {
1220            panic!("lhs must be Add");
1221        };
1222    }
1223
1224    #[test]
1225    fn comparison_binds_weaker_than_arith() {
1226        // a + 1 = b - 2
1227        //   →  Eq(Add(a, 1), Sub(b, 2))
1228        let e = parse("a + 1 = b - 2");
1229        let Expr::BinaryOp {
1230            op: BinOp::Eq,
1231            lhs,
1232            rhs,
1233            ..
1234        } = e
1235        else {
1236            panic!("root must be Eq");
1237        };
1238        assert!(matches!(*lhs, Expr::BinaryOp { op: BinOp::Add, .. }));
1239        assert!(matches!(*rhs, Expr::BinaryOp { op: BinOp::Sub, .. }));
1240    }
1241
1242    #[test]
1243    fn and_binds_tighter_than_or() {
1244        // a OR b AND c  →  Or(a, And(b, c))
1245        let e = parse("a OR b AND c");
1246        let Expr::BinaryOp {
1247            op: BinOp::Or, rhs, ..
1248        } = e
1249        else {
1250            panic!("root must be Or");
1251        };
1252        assert!(matches!(*rhs, Expr::BinaryOp { op: BinOp::And, .. }));
1253    }
1254
1255    #[test]
1256    fn unary_negation() {
1257        let e = parse("-a");
1258        let Expr::UnaryOp {
1259            op: UnaryOp::Neg, ..
1260        } = e
1261        else {
1262            panic!("expected unary Neg");
1263        };
1264    }
1265
1266    #[test]
1267    fn unary_not() {
1268        let e = parse("NOT a");
1269        let Expr::UnaryOp {
1270            op: UnaryOp::Not, ..
1271        } = e
1272        else {
1273            panic!("expected unary Not");
1274        };
1275    }
1276
1277    #[test]
1278    fn concat_operator() {
1279        let e = parse("'hello' || name");
1280        let Expr::BinaryOp {
1281            op: BinOp::Concat, ..
1282        } = e
1283        else {
1284            panic!("expected Concat");
1285        };
1286    }
1287
1288    #[test]
1289    fn cast_expr() {
1290        let e = parse("CAST(age AS TEXT)");
1291        let Expr::Cast { target, .. } = e else {
1292            panic!("expected Cast");
1293        };
1294        assert_eq!(target, DataType::Text);
1295    }
1296
1297    #[test]
1298    fn case_expr() {
1299        let e = parse("CASE WHEN a = 1 THEN 'one' WHEN a = 2 THEN 'two' ELSE 'other' END");
1300        let Expr::Case {
1301            branches, else_, ..
1302        } = e
1303        else {
1304            panic!("expected Case");
1305        };
1306        assert_eq!(branches.len(), 2);
1307        assert!(else_.is_some());
1308    }
1309
1310    #[test]
1311    fn is_null_postfix() {
1312        let e = parse("name IS NULL");
1313        assert!(matches!(e, Expr::IsNull { negated: false, .. }));
1314    }
1315
1316    #[test]
1317    fn is_not_null_postfix() {
1318        let e = parse("name IS NOT NULL");
1319        assert!(matches!(e, Expr::IsNull { negated: true, .. }));
1320    }
1321
1322    #[test]
1323    fn between_with_columns() {
1324        let e = parse("temp BETWEEN min_t AND max_t");
1325        let Expr::Between {
1326            target,
1327            low,
1328            high,
1329            negated,
1330            ..
1331        } = e
1332        else {
1333            panic!("expected Between");
1334        };
1335        assert!(!negated);
1336        assert!(matches!(*target, Expr::Column { .. }));
1337        assert!(matches!(*low, Expr::Column { .. }));
1338        assert!(matches!(*high, Expr::Column { .. }));
1339    }
1340
1341    #[test]
1342    fn not_between_negates() {
1343        let e = parse("temp NOT BETWEEN 0 AND 100");
1344        let Expr::Between { negated: true, .. } = e else {
1345            panic!("expected negated Between");
1346        };
1347    }
1348
1349    #[test]
1350    fn in_list_literal() {
1351        let e = parse("status IN (1, 2, 3)");
1352        let Expr::InList {
1353            values, negated, ..
1354        } = e
1355        else {
1356            panic!("expected InList");
1357        };
1358        assert!(!negated);
1359        assert_eq!(values.len(), 3);
1360    }
1361
1362    #[test]
1363    fn not_in_list() {
1364        let e = parse("status NOT IN (1, 2)");
1365        let Expr::InList { negated: true, .. } = e else {
1366            panic!("expected negated InList");
1367        };
1368    }
1369
1370    #[test]
1371    fn function_call_with_args() {
1372        let e = parse("UPPER(name)");
1373        let Expr::FunctionCall { name, args, .. } = e else {
1374            panic!("expected FunctionCall");
1375        };
1376        assert_eq!(name, "UPPER");
1377        assert_eq!(args.len(), 1);
1378    }
1379
1380    #[test]
1381    fn nested_function_call() {
1382        let e = parse("COALESCE(a, UPPER(b))");
1383        let Expr::FunctionCall { name, args, .. } = e else {
1384            panic!("expected FunctionCall");
1385        };
1386        assert_eq!(name, "COALESCE");
1387        assert_eq!(args.len(), 2);
1388        assert!(matches!(&args[1], Expr::FunctionCall { .. }));
1389    }
1390
1391    #[test]
1392    fn duration_literal_parses_as_text() {
1393        let e = parse("time_bucket(5m)");
1394        let Expr::FunctionCall { name, args, .. } = e else {
1395            panic!("expected FunctionCall, got {e:?}");
1396        };
1397        assert_eq!(name.to_uppercase(), "TIME_BUCKET");
1398        assert_eq!(args.len(), 1);
1399        assert!(
1400            matches!(&args[0], Expr::Literal { value: Value::Text(s), .. } if s.as_ref() == "5m"),
1401            "expected Text(\"5m\"), got {:?}",
1402            args[0]
1403        );
1404    }
1405
1406    #[test]
1407    fn placeholder_dollar_one() {
1408        let e = parse("$1");
1409        match e {
1410            Expr::Parameter { index: 0, .. } => {}
1411            other => panic!("expected Parameter(0), got {other:?}"),
1412        }
1413    }
1414
1415    #[test]
1416    fn placeholder_dollar_n() {
1417        let e = parse("$7");
1418        match e {
1419            Expr::Parameter { index: 6, .. } => {}
1420            other => panic!("expected Parameter(6), got {other:?}"),
1421        }
1422    }
1423
1424    #[test]
1425    fn placeholder_in_string_literal_is_text() {
1426        // `$1` inside a string literal must NOT parse as a placeholder.
1427        let e = parse("'$1'");
1428        match e {
1429            Expr::Literal {
1430                value: Value::Text(s),
1431                ..
1432            } if s.as_ref() == "$1" => {}
1433            other => panic!("expected text literal '$1', got {other:?}"),
1434        }
1435    }
1436
1437    #[test]
1438    fn placeholder_in_comparison() {
1439        // SELECT-WHERE shape: `id = $1`
1440        let e = parse("id = $1");
1441        let Expr::BinaryOp {
1442            op: BinOp::Eq, rhs, ..
1443        } = e
1444        else {
1445            panic!("root must be Eq");
1446        };
1447        assert!(matches!(*rhs, Expr::Parameter { index: 0, .. }));
1448    }
1449
1450    #[test]
1451    fn placeholder_zero_rejected() {
1452        let mut parser = Parser::new("$0").expect("lexer");
1453        let err = parser.parse_expr().unwrap_err();
1454        assert!(err.to_string().contains("placeholder"));
1455    }
1456
1457    #[test]
1458    fn placeholder_question_single() {
1459        // Lone `?` numbered as parameter 1 (index 0).
1460        let e = parse("?");
1461        match e {
1462            Expr::Parameter { index: 0, .. } => {}
1463            other => panic!("expected Parameter(0), got {other:?}"),
1464        }
1465    }
1466
1467    #[test]
1468    fn placeholder_question_numbered() {
1469        let e = parse("?7");
1470        match e {
1471            Expr::Parameter { index: 6, .. } => {}
1472            other => panic!("expected Parameter(6), got {other:?}"),
1473        }
1474    }
1475
1476    #[test]
1477    fn placeholder_question_numbered_zero_rejected() {
1478        let mut parser = Parser::new("?0").expect("lexer");
1479        let err = parser.parse_expr().unwrap_err();
1480        assert!(err.to_string().contains("placeholder"));
1481    }
1482
1483    #[test]
1484    fn placeholder_question_left_to_right() {
1485        // `id = ? AND name = ?` → params 0 and 1
1486        let e = parse("id = ? AND name = ?");
1487        let Expr::BinaryOp {
1488            op: BinOp::And,
1489            lhs,
1490            rhs,
1491            ..
1492        } = e
1493        else {
1494            panic!("root must be And");
1495        };
1496        let Expr::BinaryOp {
1497            op: BinOp::Eq,
1498            rhs: r1,
1499            ..
1500        } = *lhs
1501        else {
1502            panic!("lhs must be Eq");
1503        };
1504        assert!(matches!(*r1, Expr::Parameter { index: 0, .. }));
1505        let Expr::BinaryOp {
1506            op: BinOp::Eq,
1507            rhs: r2,
1508            ..
1509        } = *rhs
1510        else {
1511            panic!("rhs must be Eq");
1512        };
1513        assert!(matches!(*r2, Expr::Parameter { index: 1, .. }));
1514    }
1515
1516    #[test]
1517    fn placeholder_question_in_string_literal_is_text() {
1518        let e = parse("'?'");
1519        match e {
1520            Expr::Literal {
1521                value: Value::Text(s),
1522                ..
1523            } if s.as_ref() == "?" => {}
1524            other => panic!("expected text literal '?', got {other:?}"),
1525        }
1526    }
1527
1528    #[test]
1529    fn placeholder_mixing_question_then_dollar_rejected() {
1530        let mut parser = Parser::new("id = ? AND x = $2").expect("lexer");
1531        let err = parser.parse_expr().err().expect("should fail");
1532        assert!(
1533            err.to_string().contains("mix"),
1534            "expected mixing error, got: {err}"
1535        );
1536    }
1537
1538    #[test]
1539    fn placeholder_mixing_dollar_then_question_rejected() {
1540        let mut parser = Parser::new("id = $1 AND x = ?").expect("lexer");
1541        let err = parser.parse_expr().err().expect("should fail");
1542        assert!(
1543            err.to_string().contains("mix"),
1544            "expected mixing error, got: {err}"
1545        );
1546    }
1547
1548    #[test]
1549    fn placeholder_question_in_comment_ignored() {
1550        // `?` inside an SQL line comment must not bump the counter.
1551        // The expression after the comment is the only param.
1552        let mut parser = Parser::new("-- ? ignored\n  ?").expect("lexer");
1553        let e = parser.parse_expr().expect("parse_expr");
1554        match e {
1555            Expr::Parameter { index: 0, .. } => {}
1556            other => panic!("expected Parameter(0), got {other:?}"),
1557        }
1558    }
1559
1560    #[test]
1561    fn span_tracks_token_range() {
1562        // A literal's span must cover the exact tokens consumed.
1563        let mut parser = Parser::new("123 + 456").expect("lexer");
1564        let e = parser.parse_expr().expect("parse_expr");
1565        let span = e.span();
1566        assert!(!span.is_synthetic(), "root span must be real");
1567        assert!(span.start.offset < span.end.offset);
1568    }
1569
1570    // ====================================================================
1571    // Window OVER clause — issue #589 slice 7a
1572    // ====================================================================
1573
1574    fn try_parse(input: &str) -> Result<Expr, ParseError> {
1575        let mut parser = Parser::new(input).expect("lexer init");
1576        parser.parse_expr()
1577    }
1578
1579    #[test]
1580    fn window_lag_partition_and_order() {
1581        let e = parse("LAG(ts) OVER (PARTITION BY user_id ORDER BY ts)");
1582        let Expr::WindowFunctionCall {
1583            name, args, window, ..
1584        } = e
1585        else {
1586            panic!("expected WindowFunctionCall");
1587        };
1588        assert_eq!(name.to_uppercase(), "LAG");
1589        assert_eq!(args.len(), 1);
1590        assert_eq!(window.partition_by.len(), 1);
1591        assert_eq!(window.order_by.len(), 1);
1592        assert!(window.order_by[0].ascending);
1593        assert!(window.frame.is_none());
1594    }
1595
1596    #[test]
1597    fn window_row_number_empty_over() {
1598        let e = parse("ROW_NUMBER() OVER ()");
1599        let Expr::WindowFunctionCall {
1600            name, args, window, ..
1601        } = e
1602        else {
1603            panic!("expected WindowFunctionCall");
1604        };
1605        assert_eq!(name.to_uppercase(), "ROW_NUMBER");
1606        assert!(args.is_empty());
1607        assert!(window.partition_by.is_empty());
1608        assert!(window.order_by.is_empty());
1609        assert!(window.frame.is_none());
1610    }
1611
1612    #[test]
1613    fn window_sum_with_frame_rows_between() {
1614        let e = parse(
1615            "SUM(amount) OVER (PARTITION BY user_id ORDER BY ts \
1616             ROWS BETWEEN 2 PRECEDING AND CURRENT ROW)",
1617        );
1618        let Expr::WindowFunctionCall { name, window, .. } = e else {
1619            panic!("expected WindowFunctionCall");
1620        };
1621        assert_eq!(name.to_uppercase(), "SUM");
1622        let frame = window.frame.expect("frame present");
1623        assert!(matches!(
1624            frame.unit,
1625            crate::storage::query::ast::WindowFrameUnit::Rows
1626        ));
1627        assert!(matches!(
1628            frame.start,
1629            crate::storage::query::ast::WindowFrameBound::Preceding(_)
1630        ));
1631        assert!(matches!(
1632            frame.end,
1633            Some(crate::storage::query::ast::WindowFrameBound::CurrentRow)
1634        ));
1635    }
1636
1637    #[test]
1638    fn window_rank_order_desc_multiple_keys() {
1639        let e = parse("RANK() OVER (ORDER BY score DESC, ts)");
1640        let Expr::WindowFunctionCall { window, .. } = e else {
1641            panic!("expected WindowFunctionCall");
1642        };
1643        assert_eq!(window.order_by.len(), 2);
1644        assert!(!window.order_by[0].ascending);
1645        assert!(window.order_by[1].ascending);
1646    }
1647
1648    #[test]
1649    fn window_unbounded_preceding_following_frame() {
1650        let e = parse(
1651            "AVG(x) OVER (ORDER BY t \
1652             RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)",
1653        );
1654        let Expr::WindowFunctionCall { window, .. } = e else {
1655            panic!("expected WindowFunctionCall");
1656        };
1657        let frame = window.frame.expect("frame present");
1658        assert!(matches!(
1659            frame.unit,
1660            crate::storage::query::ast::WindowFrameUnit::Range
1661        ));
1662        assert!(matches!(
1663            frame.start,
1664            crate::storage::query::ast::WindowFrameBound::UnboundedPreceding
1665        ));
1666        assert!(matches!(
1667            frame.end,
1668            Some(crate::storage::query::ast::WindowFrameBound::UnboundedFollowing)
1669        ));
1670    }
1671
1672    #[test]
1673    fn window_rejects_non_window_function() {
1674        // UPPER is a scalar function, not eligible for OVER.
1675        let err = try_parse("UPPER(name) OVER (PARTITION BY id)")
1676            .err()
1677            .expect("should reject scalar OVER");
1678        let msg = err.to_string();
1679        assert!(
1680            msg.contains("UPPER") || msg.contains("upper"),
1681            "error should mention function name, got: {msg}"
1682        );
1683        assert!(msg.to_ascii_uppercase().contains("OVER") || msg.contains("window"));
1684    }
1685
1686    #[test]
1687    fn window_rejects_missing_open_paren() {
1688        let err = try_parse("LAG(ts) OVER PARTITION BY user_id")
1689            .err()
1690            .expect("should reject");
1691        let msg = err.to_string();
1692        assert!(
1693            msg.contains("(") || msg.to_ascii_uppercase().contains("EXPECTED"),
1694            "got: {msg}"
1695        );
1696    }
1697
1698    #[test]
1699    fn window_rejects_invalid_frame_syntax() {
1700        // CURRENT without ROW is malformed.
1701        let err = try_parse("LAG(ts) OVER (ORDER BY ts ROWS CURRENT)")
1702            .err()
1703            .expect("should reject");
1704        let msg = err.to_string();
1705        assert!(
1706            !msg.is_empty(),
1707            "expected non-empty error for malformed frame"
1708        );
1709    }
1710
1711    #[test]
1712    fn window_first_value_with_partition_only() {
1713        let e = parse("FIRST_VALUE(price) OVER (PARTITION BY symbol)");
1714        let Expr::WindowFunctionCall {
1715            name, window, args, ..
1716        } = e
1717        else {
1718            panic!("expected WindowFunctionCall");
1719        };
1720        assert_eq!(name.to_uppercase(), "FIRST_VALUE");
1721        assert_eq!(args.len(), 1);
1722        assert_eq!(window.partition_by.len(), 1);
1723        assert!(window.order_by.is_empty());
1724    }
1725}