Skip to main content

cqlite_core/query/
select_parser.rs

1//! Advanced CQL SELECT Parser
2//!
3//! This module implements the FIRST EVER CQL SELECT parser for direct SSTable access.
4//! It provides comprehensive parsing for complex SELECT statements including:
5//! - Advanced WHERE clauses with all operators
6//! - Aggregation functions and GROUP BY
7//! - ORDER BY and LIMIT clauses
8//! - Collection operations
9//! - Subqueries and JOINs (future)
10
11// CQL (Cassandra Query Language) Reference:
12// https://cassandra.apache.org/doc/latest/cassandra/developing/cql/cql_singlefile.html
13//
14// This implements CQL v3.4.3+ for Apache Cassandra 5.0+
15// CQL is NOT SQL - it's a query language specifically designed for Cassandra's distributed architecture.
16
17use super::select_ast::*;
18use crate::{Error, Result, TableId, Value};
19
20// WRITETIME and TTL are reserved words in CQL that introduce a special
21// single-argument metadata-retrieval form. They are handled as dedicated tokens
22// so the parser can produce a first-class `SelectExpression::WriteTimeTtl`
23// rather than falling through to the generic `FunctionCall` path.
24
25/// Advanced CQL SELECT parser
26#[derive(Debug)]
27pub struct SelectParser {
28    /// Current token being parsed (always `Some` after construction; `Token::Eof` marks end)
29    current_token: Option<Token>,
30    /// Tokenizer for the input
31    tokenizer: Tokenizer,
32    /// Next 0-based positional index to assign to a `?` bind marker (Issue #961).
33    /// Markers are numbered left-to-right in source order, matching CQL's
34    /// positional-parameter binding.
35    next_bind_index: usize,
36}
37
38/// Token types for CQL parsing
39#[derive(Debug, Clone, PartialEq)]
40pub enum Token {
41    // Keywords
42    Select,
43    Distinct,
44    From,
45    Where,
46    GroupBy,
47    Having,
48    OrderBy,
49    Limit,
50    PerPartitionLimit,
51    Offset,
52    And,
53    Or,
54    Not,
55    Like,
56    In,
57    Between,
58    As,
59    Asc,
60    Desc,
61    Allow,
62    Filtering,
63    Count,
64    Sum,
65    Avg,
66    Min,
67    Max,
68    // JOIN operations are NOT supported in Cassandra CQL
69    Is,
70    Null,
71    Contains,
72    Key,
73    // Metadata-retrieval functions (CQL reserved words)
74    Writetime,
75    Ttl,
76
77    // Operators
78    Equal,            // =
79    NotEqual,         // != or <>
80    LessThan,         // <
81    LessThanEqual,    // <=
82    GreaterThan,      // >
83    GreaterThanEqual, // >=
84    Plus,             // +
85    Minus,            // -
86    Multiply,         // *
87    Divide,           // /
88    Modulo,           // %
89
90    // Literals
91    Integer(i64),
92    Float(f64),
93    String(String),
94    Boolean(bool),
95    /// Unquoted UUID literal (8-4-4-4-12 hex), e.g.
96    /// `550e8400-e29b-41d4-a716-446655440000`. Carries the 16 decoded bytes so
97    /// the parser can emit `Value::Uuid` directly (Issue #956). This is what
98    /// unblocks `WHERE <uuid_pk> = <literal>` matching and the #949
99    /// partition-targeted fast path for UUID-keyed tables.
100    Uuid([u8; 16]),
101    /// Unquoted blob literal in `0x...` hex form (an even number of hex digits,
102    /// possibly empty: `0x`). Carries the decoded bytes so the parser can emit
103    /// `Value::Blob` directly (Issue #956).
104    Blob(Vec<u8>),
105
106    // Identifiers
107    Identifier(String),
108
109    // Punctuation
110    LeftParen,    // (
111    RightParen,   // )
112    LeftBracket,  // [
113    RightBracket, // ]
114    LeftBrace,    // {
115    RightBrace,   // }
116    Comma,        // ,
117    Semicolon,    // ;
118    Dot,          // .
119    Question,     // ? (for parameters)
120
121    // Special
122    Eof,
123    Newline,
124    Whitespace,
125}
126
127/// Map an already-read identifier to its keyword token, or `None` for a plain identifier.
128///
129/// Uses ASCII-case-insensitive comparison so we never have to allocate an
130/// uppercase copy of the source text.
131fn keyword_for(ident: &str) -> Option<Token> {
132    // Sorted roughly by expected frequency / first-letter group for readability.
133    const KEYWORDS: &[(&str, Token)] = &[
134        ("SELECT", Token::Select),
135        ("DISTINCT", Token::Distinct),
136        ("FROM", Token::From),
137        ("WHERE", Token::Where),
138        ("HAVING", Token::Having),
139        ("LIMIT", Token::Limit),
140        ("OFFSET", Token::Offset),
141        ("AND", Token::And),
142        ("OR", Token::Or),
143        ("NOT", Token::Not),
144        ("LIKE", Token::Like),
145        ("IN", Token::In),
146        ("BETWEEN", Token::Between),
147        ("AS", Token::As),
148        ("ASC", Token::Asc),
149        ("DESC", Token::Desc),
150        ("ALLOW", Token::Allow),
151        ("FILTERING", Token::Filtering),
152        ("COUNT", Token::Count),
153        ("SUM", Token::Sum),
154        ("AVG", Token::Avg),
155        ("MIN", Token::Min),
156        ("MAX", Token::Max),
157        ("IS", Token::Is),
158        ("NULL", Token::Null),
159        ("CONTAINS", Token::Contains),
160        ("KEY", Token::Key),
161        ("WRITETIME", Token::Writetime),
162        ("TTL", Token::Ttl),
163        ("TRUE", Token::Boolean(true)),
164        ("FALSE", Token::Boolean(false)),
165    ];
166
167    KEYWORDS
168        .iter()
169        .find(|(kw, _)| ident.eq_ignore_ascii_case(kw))
170        .map(|(_, tok)| tok.clone())
171}
172
173/// Map an aggregate keyword token to its AST type, if any.
174fn aggregate_for(token: &Token) -> Option<AggregateType> {
175    match token {
176        Token::Count => Some(AggregateType::Count),
177        Token::Sum => Some(AggregateType::Sum),
178        Token::Avg => Some(AggregateType::Avg),
179        Token::Min => Some(AggregateType::Min),
180        Token::Max => Some(AggregateType::Max),
181        _ => None,
182    }
183}
184
185/// Simple tokenizer for CQL
186#[derive(Debug)]
187pub struct Tokenizer {
188    input: Vec<char>,
189    position: usize,
190    current_char: Option<char>,
191}
192
193impl Tokenizer {
194    pub fn new(input: &str) -> Self {
195        let chars: Vec<char> = input.chars().collect();
196        let current_char = chars.first().copied();
197
198        Self {
199            input: chars,
200            position: 0,
201            current_char,
202        }
203    }
204
205    fn advance(&mut self) {
206        self.position += 1;
207        self.current_char = self.input.get(self.position).copied();
208    }
209
210    fn peek(&self) -> Option<char> {
211        self.input.get(self.position + 1).copied()
212    }
213
214    fn skip_whitespace(&mut self) {
215        while let Some(ch) = self.current_char {
216            if ch.is_whitespace() {
217                self.advance();
218            } else {
219                break;
220            }
221        }
222    }
223
224    fn read_string(&mut self, quote_char: char) -> Result<String> {
225        let mut value = String::new();
226        self.advance(); // Skip opening quote
227
228        while let Some(ch) = self.current_char {
229            if ch == quote_char {
230                self.advance(); // Skip closing quote
231                return Ok(value);
232            } else if ch == '\\' {
233                self.advance();
234                if let Some(escaped) = self.current_char {
235                    let mapped = match escaped {
236                        'n' => Some('\n'),
237                        't' => Some('\t'),
238                        'r' => Some('\r'),
239                        '\\' => Some('\\'),
240                        '\'' => Some('\''),
241                        '"' => Some('"'),
242                        _ => None,
243                    };
244                    match mapped {
245                        Some(c) => value.push(c),
246                        None => {
247                            value.push('\\');
248                            value.push(escaped);
249                        }
250                    }
251                    self.advance();
252                }
253            } else {
254                value.push(ch);
255                self.advance();
256            }
257        }
258
259        Err(Error::cql_parse("Unterminated string literal"))
260    }
261
262    fn read_number(&mut self) -> Result<Token> {
263        let mut value = String::new();
264        let mut has_dot = false;
265
266        while let Some(ch) = self.current_char {
267            if ch.is_ascii_digit() {
268                value.push(ch);
269                self.advance();
270            } else if ch == '.' && !has_dot {
271                has_dot = true;
272                value.push(ch);
273                self.advance();
274            } else {
275                break;
276            }
277        }
278
279        if has_dot {
280            value
281                .parse::<f64>()
282                .map(Token::Float)
283                .map_err(|_| Error::cql_parse(format!("Invalid float: {}", value)))
284        } else {
285            value
286                .parse::<i64>()
287                .map(Token::Integer)
288                .map_err(|_| Error::cql_parse(format!("Invalid integer: {}", value)))
289        }
290    }
291
292    /// Attempt to read an unquoted UUID literal at the current position.
293    ///
294    /// A CQL UUID literal has the fixed 36-character shape
295    /// `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx` (8-4-4-4-12 ASCII hex digits, dashes
296    /// in fixed positions, case-insensitive). Because a UUID can begin with a
297    /// digit (`5...`) or a letter (`a...`), this is tried *before* the number and
298    /// identifier paths in `next_token`.
299    ///
300    /// Returns `Some(token)` and consumes exactly 36 characters only when the
301    /// full pattern matches; otherwise returns `None` and consumes nothing, so
302    /// the caller falls back to number/identifier lexing. This non-greedy,
303    /// all-or-nothing match is what keeps `5` (integer), `a716` (identifier), and
304    /// `a - b` (subtraction) lexing unchanged.
305    fn try_read_uuid(&mut self) -> Option<Token> {
306        // Fixed UUID layout: groups of hex-digit counts separated by dashes.
307        const GROUPS: [usize; 5] = [8, 4, 4, 4, 12];
308        const UUID_LEN: usize = 36; // 32 hex digits + 4 dashes
309
310        // Peek the full 36-char window without consuming.
311        let window: Vec<char> = self
312            .input
313            .get(self.position..self.position + UUID_LEN)?
314            .to_vec();
315
316        // The character immediately after the window must not extend the token
317        // (e.g. a 37th hex digit or a trailing identifier char), otherwise this
318        // is not a standalone UUID literal.
319        if let Some(next) = self.input.get(self.position + UUID_LEN) {
320            if next.is_ascii_hexdigit() || next.is_alphanumeric() || *next == '_' || *next == '-' {
321                return None;
322            }
323        }
324
325        // Validate the 8-4-4-4-12 / dash structure and decode to bytes.
326        let mut bytes = [0u8; 16];
327        let mut byte_idx = 0;
328        let mut hi: Option<u8> = None;
329        let mut idx = 0;
330        for (g, &group_len) in GROUPS.iter().enumerate() {
331            if g > 0 {
332                if window.get(idx) != Some(&'-') {
333                    return None;
334                }
335                idx += 1;
336            }
337            for _ in 0..group_len {
338                let nibble = window.get(idx)?.to_digit(16)? as u8;
339                idx += 1;
340                match hi.take() {
341                    None => hi = Some(nibble),
342                    Some(h) => {
343                        bytes[byte_idx] = (h << 4) | nibble;
344                        byte_idx += 1;
345                    }
346                }
347            }
348        }
349
350        // All 36 chars consumed, all 16 bytes filled, no dangling nibble.
351        if idx != UUID_LEN || byte_idx != 16 || hi.is_some() {
352            return None;
353        }
354
355        for _ in 0..UUID_LEN {
356            self.advance();
357        }
358        Some(Token::Uuid(bytes))
359    }
360
361    /// Read a `0x...` blob hex literal, assuming the current char is `0` and the
362    /// next is `x`/`X`. CQL requires an even number of hex digits; `0x` (empty
363    /// blob) is valid. Errors on an odd digit count or a non-hex character.
364    fn read_blob_hex(&mut self) -> Result<Token> {
365        self.advance(); // consume '0'
366        self.advance(); // consume 'x' / 'X'
367
368        let mut digits = String::new();
369        while let Some(ch) = self.current_char {
370            if ch.is_ascii_hexdigit() {
371                digits.push(ch);
372                self.advance();
373            } else {
374                break;
375            }
376        }
377
378        if digits.len() % 2 != 0 {
379            return Err(Error::cql_parse(format!(
380                "Blob literal must have an even number of hex digits, got {} in 0x{}",
381                digits.len(),
382                digits
383            )));
384        }
385
386        let mut bytes = Vec::with_capacity(digits.len() / 2);
387        let chars: Vec<char> = digits.chars().collect();
388        for pair in chars.chunks(2) {
389            let hi = pair[0]
390                .to_digit(16)
391                .ok_or_else(|| Error::cql_parse("Invalid hex digit in blob literal"))?
392                as u8;
393            let lo = pair[1]
394                .to_digit(16)
395                .ok_or_else(|| Error::cql_parse("Invalid hex digit in blob literal"))?
396                as u8;
397            bytes.push((hi << 4) | lo);
398        }
399
400        Ok(Token::Blob(bytes))
401    }
402
403    fn read_identifier(&mut self) -> String {
404        let mut value = String::new();
405
406        while let Some(ch) = self.current_char {
407            if ch.is_alphanumeric() || ch == '_' {
408                value.push(ch);
409                self.advance();
410            } else {
411                break;
412            }
413        }
414
415        value
416    }
417
418    /// Consume the literal keyword `BY` (case-insensitive) following GROUP/ORDER.
419    fn expect_by_keyword(&mut self, after: &str) -> Result<()> {
420        self.expect_keyword_word("BY", after)
421    }
422
423    /// Consume the literal `word` (case-insensitive) as the next identifier,
424    /// erroring if it is absent. Used to resolve multi-word keywords (e.g. the
425    /// `PARTITION`/`LIMIT` words of `PER PARTITION LIMIT`).
426    fn expect_keyword_word(&mut self, word: &str, after: &str) -> Result<()> {
427        self.skip_whitespace();
428        let next = self.read_identifier();
429        if next.eq_ignore_ascii_case(word) {
430            Ok(())
431        } else {
432            Err(Error::cql_parse(format!(
433                "Expected {} after {}",
434                word, after
435            )))
436        }
437    }
438
439    pub fn next_token(&mut self) -> Result<Token> {
440        loop {
441            let ch = match self.current_char {
442                None => return Ok(Token::Eof),
443                Some(c) => c,
444            };
445
446            // Single-character punctuation / operators that don't need lookahead.
447            let single = match ch {
448                '(' => Some(Token::LeftParen),
449                ')' => Some(Token::RightParen),
450                '[' => Some(Token::LeftBracket),
451                ']' => Some(Token::RightBracket),
452                '{' => Some(Token::LeftBrace),
453                '}' => Some(Token::RightBrace),
454                ',' => Some(Token::Comma),
455                ';' => Some(Token::Semicolon),
456                '.' => Some(Token::Dot),
457                '?' => Some(Token::Question),
458                '+' => Some(Token::Plus),
459                '-' => Some(Token::Minus),
460                '*' => Some(Token::Multiply),
461                '/' => Some(Token::Divide),
462                '%' => Some(Token::Modulo),
463                '=' => Some(Token::Equal),
464                _ => None,
465            };
466            if let Some(tok) = single {
467                self.advance();
468                return Ok(tok);
469            }
470
471            match ch {
472                c if c.is_whitespace() => self.skip_whitespace(),
473                '!' => {
474                    if self.peek() == Some('=') {
475                        self.advance();
476                        self.advance();
477                        return Ok(Token::NotEqual);
478                    }
479                    return Err(Error::cql_parse("Unexpected character: !"));
480                }
481                '<' => {
482                    return Ok(match self.peek() {
483                        Some('=') => {
484                            self.advance();
485                            self.advance();
486                            Token::LessThanEqual
487                        }
488                        Some('>') => {
489                            self.advance();
490                            self.advance();
491                            Token::NotEqual
492                        }
493                        _ => {
494                            self.advance();
495                            Token::LessThan
496                        }
497                    });
498                }
499                '>' => {
500                    return Ok(if self.peek() == Some('=') {
501                        self.advance();
502                        self.advance();
503                        Token::GreaterThanEqual
504                    } else {
505                        self.advance();
506                        Token::GreaterThan
507                    });
508                }
509                '\'' | '"' => return self.read_string(ch).map(Token::String),
510                // Unquoted UUID literals (8-4-4-4-12 hex) may begin with either a
511                // digit or a letter, so probe for the full 36-char pattern before
512                // falling through to number / blob / identifier lexing. The probe
513                // is all-or-nothing and consumes nothing on a miss, so it never
514                // alters how `5`, `0xff`, or bare identifiers tokenize.
515                c if c.is_ascii_hexdigit() => {
516                    if let Some(tok) = self.try_read_uuid() {
517                        return Ok(tok);
518                    }
519                    // `0x...` blob literal (only when not already matched as a UUID).
520                    if c == '0' && matches!(self.peek(), Some('x') | Some('X')) {
521                        return self.read_blob_hex();
522                    }
523                    if c.is_ascii_digit() {
524                        return self.read_number();
525                    }
526                    // Hex letter (a-f / A-F) that was not part of a UUID: it is the
527                    // start of an ordinary identifier.
528                    let identifier = self.read_identifier();
529                    return self.classify_identifier(identifier);
530                }
531                c if c.is_ascii_digit() => return self.read_number(),
532                c if c.is_alphabetic() || c == '_' => {
533                    let identifier = self.read_identifier();
534                    return self.classify_identifier(identifier);
535                }
536                other => return Err(Error::cql_parse(format!("Unexpected character: {}", other))),
537            }
538        }
539    }
540
541    /// Resolve an already-read identifier into its token: a multi-word keyword
542    /// (`GROUP BY`, `ORDER BY`, `PER PARTITION LIMIT`), a single-word keyword,
543    /// or a plain [`Token::Identifier`]. Shared by the alphabetic and hex-letter
544    /// lexer branches so both treat keywords identically.
545    fn classify_identifier(&mut self, identifier: String) -> Result<Token> {
546        // GROUP BY / ORDER BY are two-word keywords; resolve here so the parser
547        // only ever sees a single GroupBy / OrderBy token.
548        if identifier.eq_ignore_ascii_case("GROUP") {
549            self.expect_by_keyword("GROUP")?;
550            return Ok(Token::GroupBy);
551        }
552        if identifier.eq_ignore_ascii_case("ORDER") {
553            self.expect_by_keyword("ORDER")?;
554            return Ok(Token::OrderBy);
555        }
556        // PER PARTITION LIMIT is a three-word keyword; resolve it here so the
557        // parser only ever sees a single token.
558        if identifier.eq_ignore_ascii_case("PER") {
559            self.expect_keyword_word("PARTITION", "PER")?;
560            self.expect_keyword_word("LIMIT", "PER PARTITION")?;
561            return Ok(Token::PerPartitionLimit);
562        }
563        Ok(keyword_for(&identifier).unwrap_or(Token::Identifier(identifier)))
564    }
565}
566
567impl SelectParser {
568    /// Create a new SELECT parser
569    pub fn new(cql: &str) -> Result<Self> {
570        let mut tokenizer = Tokenizer::new(cql);
571        let current_token = Some(tokenizer.next_token()?);
572        Ok(Self {
573            current_token,
574            tokenizer,
575            next_bind_index: 0,
576        })
577    }
578
579    /// Advance to the next token
580    fn advance(&mut self) -> Result<()> {
581        self.current_token = Some(self.tokenizer.next_token()?);
582        Ok(())
583    }
584
585    /// Borrow the current token. Returns `&Token::Eof` if for some reason the
586    /// stream is exhausted (in practice `current_token` is always `Some`).
587    fn peek(&self) -> &Token {
588        self.current_token.as_ref().unwrap_or(&Token::Eof)
589    }
590
591    /// True if the current token equals `tok` (by discriminant, not payload).
592    fn at(&self, tok: &Token) -> bool {
593        self.current_token
594            .as_ref()
595            .is_some_and(|cur| std::mem::discriminant(cur) == std::mem::discriminant(tok))
596    }
597
598    /// Consume the current token if it matches `tok` (by discriminant); return whether it did.
599    fn eat(&mut self, tok: &Token) -> Result<bool> {
600        if self.at(tok) {
601            self.advance()?;
602            Ok(true)
603        } else {
604            Ok(false)
605        }
606    }
607
608    /// Check if current token matches expected token
609    fn expect(&mut self, expected: Token) -> Result<()> {
610        if let Some(ref current) = self.current_token {
611            if std::mem::discriminant(current) == std::mem::discriminant(&expected) {
612                self.advance()?;
613                Ok(())
614            } else {
615                Err(Error::cql_parse(format!(
616                    "Expected {:?}, found {:?}",
617                    expected, current
618                )))
619            }
620        } else {
621            Err(Error::cql_parse("Unexpected end of input"))
622        }
623    }
624
625    /// Consume an integer literal token (used by LIMIT and OFFSET parsers).
626    fn expect_integer(&mut self, context: &str) -> Result<i64> {
627        if let Some(Token::Integer(n)) = self.current_token {
628            self.advance()?;
629            Ok(n)
630        } else {
631            Err(Error::cql_parse(format!(
632                "Expected integer after {}",
633                context
634            )))
635        }
636    }
637
638    /// Parse `name` or `name . column` into a [`ColumnRef`], assuming the
639    /// current token is the leading identifier.
640    fn parse_column_ref(&mut self, table_or_column: String) -> Result<ColumnRef> {
641        // Caller has already consumed the leading identifier.
642        if !self.eat(&Token::Dot)? {
643            return Ok(ColumnRef::new(table_or_column));
644        }
645        if let Some(Token::Identifier(column)) = self.current_token.clone() {
646            self.advance()?;
647            Ok(ColumnRef::qualified(table_or_column, column))
648        } else {
649            Err(Error::cql_parse(
650                "Expected column name after table qualifier",
651            ))
652        }
653    }
654
655    /// Parse a complete SELECT statement
656    pub fn parse_select_statement(&mut self) -> Result<SelectStatement> {
657        self.expect(Token::Select)?;
658        let select_clause = self.parse_select_clause()?;
659
660        let from_clause = if self.eat(&Token::From)? {
661            Some(self.parse_from_clause()?)
662        } else {
663            None
664        };
665
666        let where_clause = if self.eat(&Token::Where)? {
667            Some(self.parse_where_expression()?)
668        } else {
669            None
670        };
671
672        let group_by = if self.eat(&Token::GroupBy)? {
673            Some(self.parse_group_by_clause()?)
674        } else {
675            None
676        };
677
678        let having_clause = if self.eat(&Token::Having)? {
679            Some(self.parse_where_expression()?)
680        } else {
681            None
682        };
683
684        let order_by = if self.eat(&Token::OrderBy)? {
685            Some(self.parse_order_by_clause()?)
686        } else {
687            None
688        };
689
690        // PER PARTITION LIMIT precedes the query-wide LIMIT in CQL grammar.
691        let per_partition_limit = if self.eat(&Token::PerPartitionLimit)? {
692            Some(self.parse_positive_limit("PER PARTITION LIMIT")?)
693        } else {
694            None
695        };
696
697        let limit = if self.eat(&Token::Limit)? {
698            Some(self.parse_limit_clause()?)
699        } else {
700            None
701        };
702
703        let offset = if self.eat(&Token::Offset)? {
704            Some(self.expect_integer("OFFSET")? as u64)
705        } else {
706            None
707        };
708
709        let allow_filtering = if self.eat(&Token::Allow)? {
710            self.expect(Token::Filtering)?;
711            true
712        } else {
713            false
714        };
715
716        // PER PARTITION LIMIT must precede LIMIT (and any trailing OFFSET/ALLOW
717        // FILTERING). Checking here — after every trailing clause is consumed —
718        // rejects all mis-orderings instead of silently ignoring the clause,
719        // including `LIMIT n OFFSET m PER PARTITION LIMIT k` (roborev job 38).
720        if self.at(&Token::PerPartitionLimit) {
721            return Err(Error::cql_parse(
722                "PER PARTITION LIMIT must appear before LIMIT",
723            ));
724        }
725
726        Ok(SelectStatement {
727            select_clause,
728            from_clause,
729            where_clause,
730            group_by,
731            having_clause,
732            order_by,
733            limit,
734            per_partition_limit,
735            offset,
736            allow_filtering,
737        })
738    }
739
740    /// Parse SELECT clause
741    fn parse_select_clause(&mut self) -> Result<SelectClause> {
742        let distinct = self.eat(&Token::Distinct)?;
743
744        if self.eat(&Token::Multiply)? {
745            return Ok(SelectClause::All);
746        }
747
748        let mut expressions = Vec::new();
749        loop {
750            expressions.push(self.parse_select_expression()?);
751            if !self.eat(&Token::Comma)? {
752                break;
753            }
754        }
755
756        if distinct {
757            Ok(SelectClause::Distinct(expressions))
758        } else {
759            Ok(SelectClause::Columns(expressions))
760        }
761    }
762
763    /// Parse a single SELECT expression
764    fn parse_select_expression(&mut self) -> Result<SelectExpression> {
765        let expr = self.parse_primary_expression()?;
766
767        // Check for AS alias
768        if self.eat(&Token::As)? {
769            if let Some(Token::Identifier(alias)) = self.current_token.clone() {
770                self.advance()?;
771                return Ok(SelectExpression::Aliased(Box::new(expr), alias));
772            }
773            return Err(Error::cql_parse("Expected alias name after AS"));
774        }
775
776        Ok(expr)
777    }
778
779    /// Parse primary expression (column, function, literal, etc.)
780    fn parse_primary_expression(&mut self) -> Result<SelectExpression> {
781        if let Some(agg) = aggregate_for(self.peek()) {
782            self.advance()?;
783            return self.parse_aggregate_function(agg);
784        }
785
786        // WRITETIME(col) and TTL(col) — first-class metadata-retrieval functions.
787        // They tokenize as dedicated keywords so they are caught here before the
788        // generic identifier path.
789        if matches!(self.peek(), Token::Writetime | Token::Ttl) {
790            let function = match self.current_token.clone() {
791                Some(Token::Writetime) => WriteTimeTtlFunction::WriteTime,
792                Some(Token::Ttl) => WriteTimeTtlFunction::Ttl,
793                _ => unreachable!("peek guard ensures only Writetime or Ttl here"),
794            };
795            self.advance()?;
796            return self.parse_writetime_ttl_call(function);
797        }
798
799        // Unary minus on a numeric literal (e.g. `token(pk) >= -1000`). Partition
800        // tokens span the full i64 range, so negative bounds are essential for
801        // token-range restrictions (Issue #955). Only a bare negative number is
802        // supported here; arbitrary unary-minus expressions are out of scope.
803        if matches!(self.peek(), Token::Minus) {
804            self.advance()?;
805            return match self.current_token.clone() {
806                Some(Token::Integer(n)) => {
807                    self.advance()?;
808                    Ok(SelectExpression::Literal(Value::BigInt(-n)))
809                }
810                Some(Token::Float(f)) => {
811                    self.advance()?;
812                    Ok(SelectExpression::Literal(Value::Float(-f)))
813                }
814                other => Err(Error::cql_parse(format!(
815                    "Expected a numeric literal after unary minus, found: {other:?}"
816                ))),
817            };
818        }
819
820        // Take ownership/copy of literal payloads up front so we can call &mut self.
821        match self.current_token.clone() {
822            Some(Token::Identifier(name)) => {
823                self.advance()?;
824
825                // Function call: identifier ( args )
826                if self.eat(&Token::LeftParen)? {
827                    let mut args = Vec::new();
828                    if !self.at(&Token::RightParen) {
829                        loop {
830                            args.push(self.parse_select_expression()?);
831                            if !self.eat(&Token::Comma)? {
832                                break;
833                            }
834                        }
835                    }
836                    self.expect(Token::RightParen)?;
837                    return Ok(SelectExpression::Function(FunctionCall { name, args }));
838                }
839
840                // Either bare column or qualified table.column.
841                let col = self.parse_column_ref(name)?;
842                Ok(SelectExpression::Column(col))
843            }
844            Some(Token::Integer(n)) => {
845                self.advance()?;
846                Ok(SelectExpression::Literal(Value::BigInt(n)))
847            }
848            Some(Token::Float(f)) => {
849                self.advance()?;
850                Ok(SelectExpression::Literal(Value::Float(f)))
851            }
852            Some(Token::String(s)) => {
853                self.advance()?;
854                Ok(SelectExpression::Literal(Value::Text(s)))
855            }
856            Some(Token::Uuid(bytes)) => {
857                self.advance()?;
858                Ok(SelectExpression::Literal(Value::Uuid(bytes)))
859            }
860            Some(Token::Blob(bytes)) => {
861                self.advance()?;
862                Ok(SelectExpression::Literal(Value::Blob(bytes)))
863            }
864            Some(Token::Boolean(b)) => {
865                self.advance()?;
866                Ok(SelectExpression::Literal(Value::Boolean(b)))
867            }
868            Some(Token::Null) => {
869                self.advance()?;
870                Ok(SelectExpression::Literal(Value::Null))
871            }
872            // Positional `?` bind marker (Issue #961). Allowed anywhere a value
873            // expression is — the RHS of a comparison, an IN value list, a
874            // BETWEEN range bound, or a SELECT-list literal. The 0-based index is
875            // assigned left-to-right in source order and resolved at bind time.
876            Some(Token::Question) => {
877                let index = self.next_bind_index;
878                self.next_bind_index += 1;
879                self.advance()?;
880                Ok(SelectExpression::BindMarker(index))
881            }
882            Some(Token::LeftParen) => {
883                self.advance()?;
884                let expr = self.parse_select_expression()?;
885                self.expect(Token::RightParen)?;
886                Ok(expr)
887            }
888            other => Err(Error::cql_parse(format!(
889                "Unexpected token in expression: {:?}",
890                other
891            ))),
892        }
893    }
894
895    /// Parse `WRITETIME(col)` or `TTL(col)`.
896    ///
897    /// The function keyword has already been consumed by the caller.
898    /// Grammar: `'(' identifier ')'` optionally followed by `AS alias`.
899    fn parse_writetime_ttl_call(
900        &mut self,
901        function: WriteTimeTtlFunction,
902    ) -> Result<SelectExpression> {
903        self.expect(Token::LeftParen)?;
904
905        // Argument must be a single bare identifier (the column name).
906        let column = match self.current_token.clone() {
907            Some(Token::Identifier(name)) => {
908                self.advance()?;
909                name
910            }
911            other => {
912                return Err(Error::cql_parse(format!(
913                    "{} requires a single column name argument, found: {:?}",
914                    match function {
915                        WriteTimeTtlFunction::WriteTime => "WRITETIME",
916                        WriteTimeTtlFunction::Ttl => "TTL",
917                    },
918                    other
919                )));
920            }
921        };
922
923        self.expect(Token::RightParen)?;
924
925        // Optional alias: `WRITETIME(col) AS wt`
926        // Aliases in SELECT are supported by the grammar (the surrounding
927        // `parse_select_expression` already handles `AS`), but we handle it here
928        // too so we can attach it directly to the `WriteTimeTtlCall` for clarity.
929        // The outer `parse_select_expression` wraps us in `Aliased` when it sees
930        // `AS`; that path is the canonical one and this variant stores it inline.
931        let alias = if self.eat(&Token::As)? {
932            match self.current_token.clone() {
933                Some(Token::Identifier(alias_name)) => {
934                    self.advance()?;
935                    Some(alias_name)
936                }
937                other => {
938                    return Err(Error::cql_parse(format!(
939                        "Expected alias identifier after AS, found: {:?}",
940                        other
941                    )));
942                }
943            }
944        } else {
945            None
946        };
947
948        Ok(SelectExpression::WriteTimeTtl(WriteTimeTtlCall {
949            function,
950            column,
951            alias,
952        }))
953    }
954
955    /// Parse aggregate function
956    fn parse_aggregate_function(&mut self, agg_type: AggregateType) -> Result<SelectExpression> {
957        self.expect(Token::LeftParen)?;
958
959        let distinct = self.eat(&Token::Distinct)?;
960        let mut args = Vec::new();
961
962        if !self.at(&Token::RightParen) {
963            // COUNT(*) is the only place `*` is valid as an aggregate arg; treat it as a wildcard column.
964            if self.eat(&Token::Multiply)? {
965                args.push(SelectExpression::Column(ColumnRef::new("*".to_string())));
966            } else {
967                loop {
968                    args.push(self.parse_select_expression()?);
969                    if !self.eat(&Token::Comma)? {
970                        break;
971                    }
972                }
973            }
974        }
975
976        self.expect(Token::RightParen)?;
977
978        Ok(SelectExpression::Aggregate(AggregateFunction {
979            function: agg_type,
980            args,
981            distinct,
982        }))
983    }
984
985    /// Parse FROM clause
986    fn parse_from_clause(&mut self) -> Result<FromClause> {
987        // Cassandra CQL only supports single table queries - NO JOINS
988        let Some(Token::Identifier(first_identifier)) = self.current_token.clone() else {
989            return Err(Error::cql_parse("Expected table name in FROM clause"));
990        };
991        self.advance()?;
992
993        // Qualified name: keyspace.table
994        let table_name = if self.eat(&Token::Dot)? {
995            if let Some(Token::Identifier(actual_table)) = self.current_token.clone() {
996                self.advance()?;
997                format!("{}.{}", first_identifier, actual_table)
998            } else {
999                return Err(Error::cql_parse("Expected table name after keyspace"));
1000            }
1001        } else {
1002            first_identifier
1003        };
1004
1005        let table = TableId::new(table_name);
1006
1007        // Optional alias - but only if the next identifier isn't a clause keyword
1008        // that the lookahead-free tokenizer would otherwise hand us as a plain identifier.
1009        // (In practice clause keywords already tokenize as their own variants, but we
1010        // keep this defensive check to preserve historical behavior.)
1011        const CLAUSE_KEYWORDS: &[&str] = &["WHERE", "GROUP", "ORDER", "HAVING", "LIMIT"];
1012        if let Some(Token::Identifier(alias)) = self.current_token.clone() {
1013            let is_clause_kw = CLAUSE_KEYWORDS
1014                .iter()
1015                .any(|kw| alias.eq_ignore_ascii_case(kw));
1016            if !is_clause_kw {
1017                self.advance()?;
1018                return Ok(FromClause::TableAlias(table, alias));
1019            }
1020        }
1021
1022        Ok(FromClause::Table(table))
1023    }
1024
1025    /// Parse WHERE expression
1026    fn parse_where_expression(&mut self) -> Result<WhereExpression> {
1027        self.parse_or_expression()
1028    }
1029
1030    /// Parse OR expression
1031    fn parse_or_expression(&mut self) -> Result<WhereExpression> {
1032        let first = self.parse_and_expression()?;
1033        let mut or_exprs = vec![first];
1034        while self.eat(&Token::Or)? {
1035            or_exprs.push(self.parse_and_expression()?);
1036        }
1037        Ok(unwrap_singleton(or_exprs, WhereExpression::Or))
1038    }
1039
1040    /// Parse AND expression
1041    fn parse_and_expression(&mut self) -> Result<WhereExpression> {
1042        let first = self.parse_not_expression()?;
1043        let mut and_exprs = vec![first];
1044        while self.eat(&Token::And)? {
1045            and_exprs.push(self.parse_not_expression()?);
1046        }
1047        Ok(unwrap_singleton(and_exprs, WhereExpression::And))
1048    }
1049
1050    /// Parse NOT expression
1051    fn parse_not_expression(&mut self) -> Result<WhereExpression> {
1052        if self.eat(&Token::Not)? {
1053            let expr = self.parse_comparison_expression()?;
1054            Ok(WhereExpression::Not(Box::new(expr)))
1055        } else {
1056            self.parse_comparison_expression()
1057        }
1058    }
1059
1060    /// Parse comparison expression
1061    fn parse_comparison_expression(&mut self) -> Result<WhereExpression> {
1062        if self.eat(&Token::LeftParen)? {
1063            let expr = self.parse_where_expression()?;
1064            self.expect(Token::RightParen)?;
1065            return Ok(WhereExpression::Parentheses(Box::new(expr)));
1066        }
1067
1068        let left = self.parse_select_expression()?;
1069
1070        // Map a "simple" binary comparison token to its operator. For operators
1071        // with bespoke right-hand-side parsing (IN, BETWEEN, IS, CONTAINS) we
1072        // handle them in the match below and return early.
1073        let simple_op = match self.peek() {
1074            Token::Equal => Some(ComparisonOperator::Equal),
1075            Token::NotEqual => Some(ComparisonOperator::NotEqual),
1076            Token::LessThan => Some(ComparisonOperator::LessThan),
1077            Token::LessThanEqual => Some(ComparisonOperator::LessThanOrEqual),
1078            Token::GreaterThan => Some(ComparisonOperator::GreaterThan),
1079            Token::GreaterThanEqual => Some(ComparisonOperator::GreaterThanOrEqual),
1080            Token::Like => Some(ComparisonOperator::Like),
1081            _ => None,
1082        };
1083
1084        if let Some(op) = simple_op {
1085            self.advance()?;
1086            let right = ComparisonRightSide::Value(self.parse_select_expression()?);
1087            return Ok(WhereExpression::Comparison(ComparisonExpression {
1088                left,
1089                operator: op,
1090                right,
1091            }));
1092        }
1093
1094        let operator = match self.peek() {
1095            Token::In => {
1096                self.advance()?;
1097                let right = self.parse_in_expression()?;
1098                return Ok(WhereExpression::Comparison(ComparisonExpression {
1099                    left,
1100                    operator: ComparisonOperator::In,
1101                    right,
1102                }));
1103            }
1104            Token::Between => {
1105                self.advance()?;
1106                let start = self.parse_select_expression()?;
1107                self.expect(Token::And)?;
1108                let end = self.parse_select_expression()?;
1109                return Ok(WhereExpression::Comparison(ComparisonExpression {
1110                    left,
1111                    operator: ComparisonOperator::Between,
1112                    right: ComparisonRightSide::Range(start, end),
1113                }));
1114            }
1115            Token::Is => {
1116                self.advance()?;
1117                let op = if self.eat(&Token::Not)? {
1118                    ComparisonOperator::IsNotNull
1119                } else {
1120                    ComparisonOperator::IsNull
1121                };
1122                self.expect(Token::Null)?;
1123                op
1124            }
1125            Token::Contains => {
1126                self.advance()?;
1127                if self.eat(&Token::Key)? {
1128                    ComparisonOperator::ContainsKey
1129                } else {
1130                    ComparisonOperator::Contains
1131                }
1132            }
1133            other => {
1134                return Err(Error::cql_parse(format!(
1135                    "Expected comparison operator, found {:?}",
1136                    other
1137                )));
1138            }
1139        };
1140
1141        // Only IS NULL / IS NOT NULL / CONTAINS / CONTAINS KEY reach here.
1142        let right = match operator {
1143            ComparisonOperator::IsNull | ComparisonOperator::IsNotNull => {
1144                ComparisonRightSide::Value(SelectExpression::Literal(Value::Null))
1145            }
1146            _ => ComparisonRightSide::Value(self.parse_select_expression()?),
1147        };
1148
1149        Ok(WhereExpression::Comparison(ComparisonExpression {
1150            left,
1151            operator,
1152            right,
1153        }))
1154    }
1155
1156    /// Parse IN expression value list
1157    fn parse_in_expression(&mut self) -> Result<ComparisonRightSide> {
1158        self.expect(Token::LeftParen)?;
1159        let mut values = Vec::new();
1160
1161        if !self.at(&Token::RightParen) {
1162            loop {
1163                values.push(self.parse_select_expression()?);
1164                if !self.eat(&Token::Comma)? {
1165                    break;
1166                }
1167            }
1168        }
1169
1170        self.expect(Token::RightParen)?;
1171        Ok(ComparisonRightSide::ValueList(values))
1172    }
1173
1174    /// Parse GROUP BY clause
1175    fn parse_group_by_clause(&mut self) -> Result<GroupByClause> {
1176        let mut columns = Vec::new();
1177
1178        loop {
1179            let Some(Token::Identifier(name)) = self.current_token.clone() else {
1180                return Err(Error::cql_parse("Expected column name in GROUP BY"));
1181            };
1182            self.advance()?;
1183            columns.push(self.parse_column_ref(name)?);
1184
1185            if !self.eat(&Token::Comma)? {
1186                break;
1187            }
1188        }
1189
1190        Ok(GroupByClause { columns })
1191    }
1192
1193    /// Parse ORDER BY clause
1194    fn parse_order_by_clause(&mut self) -> Result<OrderByClause> {
1195        let mut items = Vec::new();
1196
1197        loop {
1198            let expression = self.parse_select_expression()?;
1199
1200            let direction = if self.eat(&Token::Desc)? {
1201                SortDirection::Descending
1202            } else if self.eat(&Token::Asc)? {
1203                SortDirection::Ascending
1204            } else {
1205                SortDirection::Ascending
1206            };
1207
1208            items.push(OrderByItem {
1209                expression,
1210                direction,
1211            });
1212
1213            if !self.eat(&Token::Comma)? {
1214                break;
1215            }
1216        }
1217
1218        Ok(OrderByClause { items })
1219    }
1220
1221    /// Parse the query-wide LIMIT clause.
1222    ///
1223    /// `LIMIT 0` is intentionally accepted and yields an empty result set
1224    /// (enforced downstream); this preserves long-standing CQLite behavior
1225    /// (`test_limit_zero_returns_empty`). Only `PER PARTITION LIMIT` is required
1226    /// to be strictly positive (Issue #757).
1227    fn parse_limit_clause(&mut self) -> Result<LimitClause> {
1228        let count = self.expect_integer("LIMIT")? as u64;
1229        Ok(LimitClause { count })
1230    }
1231
1232    /// Parse a strictly-positive integer limit, rejecting zero/negative values.
1233    /// Used for `PER PARTITION LIMIT`, which Cassandra requires to be >= 1.
1234    fn parse_positive_limit(&mut self, clause: &str) -> Result<u64> {
1235        let value = self.expect_integer(clause)?;
1236        if value < 1 {
1237            return Err(Error::cql_parse(format!(
1238                "{} must be a positive integer, got {}",
1239                clause, value
1240            )));
1241        }
1242        Ok(value as u64)
1243    }
1244}
1245
1246/// If `exprs` has a single element, return it; otherwise wrap with `wrap`
1247/// (typically `WhereExpression::And` / `WhereExpression::Or`). The vector is
1248/// guaranteed non-empty by callers that always push at least one element.
1249fn unwrap_singleton<F>(mut exprs: Vec<WhereExpression>, wrap: F) -> WhereExpression
1250where
1251    F: FnOnce(Vec<WhereExpression>) -> WhereExpression,
1252{
1253    if exprs.len() == 1 {
1254        exprs.pop().expect("checked len == 1")
1255    } else {
1256        wrap(exprs)
1257    }
1258}
1259
1260/// Main parsing function for SELECT statements
1261pub fn parse_select(cql: &str) -> Result<SelectStatement> {
1262    let mut parser = SelectParser::new(cql)?;
1263    parser.parse_select_statement()
1264}
1265
1266#[cfg(all(test, feature = "state_machine"))]
1267mod tests {
1268    use super::super::select_ast::{SelectExpression, WriteTimeTtlFunction};
1269    use super::*;
1270
1271    // --- WRITETIME / TTL parser tests (Issue #690) ---
1272
1273    #[test]
1274    fn test_writetime_basic() {
1275        let stmt = parse_select("SELECT WRITETIME(name) FROM ks.tbl").unwrap();
1276        if let SelectClause::Columns(exprs) = stmt.select_clause {
1277            assert_eq!(exprs.len(), 1);
1278            match &exprs[0] {
1279                SelectExpression::WriteTimeTtl(call) => {
1280                    assert_eq!(call.function, WriteTimeTtlFunction::WriteTime);
1281                    assert_eq!(call.column, "name");
1282                    assert!(call.alias.is_none());
1283                }
1284                other => panic!("Expected WriteTimeTtl, got: {:?}", other),
1285            }
1286        } else {
1287            panic!("Expected Columns select clause");
1288        }
1289    }
1290
1291    #[test]
1292    fn test_ttl_basic() {
1293        let stmt = parse_select("SELECT TTL(name) FROM ks.tbl").unwrap();
1294        if let SelectClause::Columns(exprs) = stmt.select_clause {
1295            assert_eq!(exprs.len(), 1);
1296            match &exprs[0] {
1297                SelectExpression::WriteTimeTtl(call) => {
1298                    assert_eq!(call.function, WriteTimeTtlFunction::Ttl);
1299                    assert_eq!(call.column, "name");
1300                    assert!(call.alias.is_none());
1301                }
1302                other => panic!("Expected WriteTimeTtl, got: {:?}", other),
1303            }
1304        } else {
1305            panic!("Expected Columns select clause");
1306        }
1307    }
1308
1309    #[test]
1310    fn test_writetime_lowercase() {
1311        // CQL is case-insensitive; the keyword should parse regardless of case.
1312        let stmt = parse_select("SELECT writetime(name) FROM tbl").unwrap();
1313        if let SelectClause::Columns(exprs) = stmt.select_clause {
1314            match &exprs[0] {
1315                SelectExpression::WriteTimeTtl(call) => {
1316                    assert_eq!(call.function, WriteTimeTtlFunction::WriteTime);
1317                }
1318                other => panic!("Expected WriteTimeTtl, got: {:?}", other),
1319            }
1320        } else {
1321            panic!("Expected Columns");
1322        }
1323    }
1324
1325    #[test]
1326    fn test_ttl_lowercase() {
1327        let stmt = parse_select("SELECT ttl(name) FROM tbl").unwrap();
1328        if let SelectClause::Columns(exprs) = stmt.select_clause {
1329            match &exprs[0] {
1330                SelectExpression::WriteTimeTtl(call) => {
1331                    assert_eq!(call.function, WriteTimeTtlFunction::Ttl);
1332                }
1333                other => panic!("Expected WriteTimeTtl, got: {:?}", other),
1334            }
1335        } else {
1336            panic!("Expected Columns");
1337        }
1338    }
1339
1340    #[test]
1341    fn test_writetime_mixed_case() {
1342        let stmt = parse_select("SELECT WriteTime(name) FROM tbl").unwrap();
1343        if let SelectClause::Columns(exprs) = stmt.select_clause {
1344            match &exprs[0] {
1345                SelectExpression::WriteTimeTtl(call) => {
1346                    assert_eq!(call.function, WriteTimeTtlFunction::WriteTime);
1347                }
1348                other => panic!("Expected WriteTimeTtl, got: {:?}", other),
1349            }
1350        } else {
1351            panic!("Expected Columns");
1352        }
1353    }
1354
1355    #[test]
1356    fn test_writetime_and_ttl_together() {
1357        let stmt = parse_select("SELECT WRITETIME(name), TTL(name) FROM ks.tbl").unwrap();
1358        if let SelectClause::Columns(exprs) = stmt.select_clause {
1359            assert_eq!(exprs.len(), 2);
1360            match &exprs[0] {
1361                SelectExpression::WriteTimeTtl(c) => {
1362                    assert_eq!(c.function, WriteTimeTtlFunction::WriteTime);
1363                }
1364                other => panic!("Expected WriteTimeTtl for first expr, got: {:?}", other),
1365            }
1366            match &exprs[1] {
1367                SelectExpression::WriteTimeTtl(c) => {
1368                    assert_eq!(c.function, WriteTimeTtlFunction::Ttl);
1369                }
1370                other => panic!("Expected WriteTimeTtl for second expr, got: {:?}", other),
1371            }
1372        } else {
1373            panic!("Expected Columns");
1374        }
1375    }
1376
1377    #[test]
1378    fn test_writetime_with_alias() {
1379        // Aliases on WRITETIME/TTL are supported; the parser captures them inline.
1380        let stmt = parse_select("SELECT WRITETIME(name) AS wt FROM tbl").unwrap();
1381        if let SelectClause::Columns(exprs) = stmt.select_clause {
1382            assert_eq!(exprs.len(), 1);
1383            match &exprs[0] {
1384                SelectExpression::WriteTimeTtl(call) => {
1385                    assert_eq!(call.function, WriteTimeTtlFunction::WriteTime);
1386                    assert_eq!(call.alias.as_deref(), Some("wt"));
1387                }
1388                other => panic!("Expected WriteTimeTtl with alias, got: {:?}", other),
1389            }
1390        } else {
1391            panic!("Expected Columns");
1392        }
1393    }
1394
1395    #[test]
1396    fn test_ttl_with_alias() {
1397        let stmt = parse_select("SELECT ttl(name) AS remaining FROM tbl").unwrap();
1398        if let SelectClause::Columns(exprs) = stmt.select_clause {
1399            match &exprs[0] {
1400                SelectExpression::WriteTimeTtl(call) => {
1401                    assert_eq!(call.function, WriteTimeTtlFunction::Ttl);
1402                    assert_eq!(call.alias.as_deref(), Some("remaining"));
1403                }
1404                other => panic!("Expected WriteTimeTtl, got: {:?}", other),
1405            }
1406        } else {
1407            panic!("Expected Columns");
1408        }
1409    }
1410
1411    #[test]
1412    fn test_writetime_alongside_plain_columns() {
1413        let stmt = parse_select("SELECT id, WRITETIME(name), name FROM tbl").unwrap();
1414        if let SelectClause::Columns(exprs) = stmt.select_clause {
1415            assert_eq!(exprs.len(), 3);
1416            assert!(matches!(&exprs[0], SelectExpression::Column(_)));
1417            assert!(matches!(&exprs[1], SelectExpression::WriteTimeTtl(_)));
1418            assert!(matches!(&exprs[2], SelectExpression::Column(_)));
1419        } else {
1420            panic!("Expected Columns");
1421        }
1422    }
1423
1424    #[test]
1425    fn test_column_name_is_preserved() {
1426        let stmt = parse_select("SELECT WRITETIME(myColumn) FROM tbl").unwrap();
1427        if let SelectClause::Columns(exprs) = stmt.select_clause {
1428            match &exprs[0] {
1429                SelectExpression::WriteTimeTtl(call) => {
1430                    // The column name is preserved as parsed (not lowercased).
1431                    assert_eq!(call.column, "myColumn");
1432                }
1433                other => panic!("Unexpected: {:?}", other),
1434            }
1435        } else {
1436            panic!("Expected Columns");
1437        }
1438    }
1439
1440    // --- existing tests (preserved) ---
1441
1442    #[test]
1443    fn test_simple_select() {
1444        let stmt = parse_select("SELECT * FROM users").unwrap();
1445        assert_eq!(stmt.select_clause, SelectClause::All);
1446        if let Some(FromClause::Table(table)) = stmt.from_clause {
1447            assert_eq!(table.name(), "users");
1448        } else {
1449            panic!("Expected Table in FROM clause");
1450        }
1451    }
1452
1453    #[test]
1454    fn test_select_with_columns() {
1455        let stmt = parse_select("SELECT id, name, email FROM users").unwrap();
1456        if let SelectClause::Columns(exprs) = stmt.select_clause {
1457            assert_eq!(exprs.len(), 3);
1458        } else {
1459            panic!("Expected Columns in SELECT clause");
1460        }
1461    }
1462
1463    #[test]
1464    fn test_select_constant() {
1465        let stmt = parse_select("SELECT 1").unwrap();
1466        assert!(stmt.from_clause.is_none());
1467        if let SelectClause::Columns(exprs) = stmt.select_clause {
1468            assert_eq!(exprs.len(), 1);
1469            if let SelectExpression::Literal(Value::BigInt(1)) = &exprs[0] {
1470                // Success
1471            } else {
1472                panic!("Expected literal BigInt 1, got: {:?}", &exprs[0]);
1473            }
1474        } else {
1475            panic!("Expected Columns in SELECT clause");
1476        }
1477    }
1478
1479    #[test]
1480    fn test_select_with_where() {
1481        let stmt = parse_select("SELECT * FROM users WHERE id = 123").unwrap();
1482        assert!(stmt.where_clause.is_some());
1483    }
1484
1485    #[test]
1486    fn test_select_with_aggregates() {
1487        let stmt = parse_select("SELECT COUNT(*), AVG(age) FROM users GROUP BY city").unwrap();
1488        assert!(stmt.requires_aggregation());
1489        assert!(stmt.group_by.is_some());
1490    }
1491
1492    #[test]
1493    fn test_complex_where_clause() {
1494        let stmt =
1495            parse_select("SELECT * FROM users WHERE age > 21 AND (city = 'NYC' OR city = 'LA')")
1496                .unwrap();
1497        assert!(stmt.where_clause.is_some());
1498    }
1499
1500    #[test]
1501    fn test_order_by_and_limit() {
1502        let stmt = parse_select("SELECT * FROM users ORDER BY created_at DESC, name ASC LIMIT 10")
1503            .unwrap();
1504        assert!(stmt.order_by.is_some());
1505        assert!(stmt.limit.is_some());
1506
1507        if let Some(limit) = stmt.limit {
1508            assert_eq!(limit.count, 10);
1509        }
1510    }
1511
1512    // --- PER PARTITION LIMIT parser tests (Issue #757) ---
1513
1514    #[test]
1515    fn test_per_partition_limit_basic() {
1516        let stmt = parse_select("SELECT * FROM ks.t PER PARTITION LIMIT 2").unwrap();
1517        assert_eq!(stmt.per_partition_limit, Some(2));
1518        assert!(stmt.limit.is_none());
1519    }
1520
1521    #[test]
1522    fn test_per_partition_limit_with_global_limit() {
1523        let stmt = parse_select("SELECT * FROM ks.t PER PARTITION LIMIT 2 LIMIT 5").unwrap();
1524        assert_eq!(stmt.per_partition_limit, Some(2));
1525        assert_eq!(stmt.limit.map(|l| l.count), Some(5));
1526    }
1527
1528    #[test]
1529    fn test_per_partition_limit_after_order_by() {
1530        let stmt = parse_select("SELECT * FROM ks.t ORDER BY c DESC PER PARTITION LIMIT 3 LIMIT 9")
1531            .unwrap();
1532        assert!(stmt.order_by.is_some());
1533        assert_eq!(stmt.per_partition_limit, Some(3));
1534        assert_eq!(stmt.limit.map(|l| l.count), Some(9));
1535    }
1536
1537    #[test]
1538    fn test_per_partition_limit_rejects_zero() {
1539        assert!(parse_select("SELECT * FROM ks.t PER PARTITION LIMIT 0").is_err());
1540    }
1541
1542    #[test]
1543    fn test_global_limit_zero_is_accepted() {
1544        // Regression: `LIMIT 0` must parse (yields empty result downstream);
1545        // only PER PARTITION LIMIT requires a strictly-positive value.
1546        let stmt = parse_select("SELECT * FROM ks.t LIMIT 0").unwrap();
1547        assert_eq!(stmt.limit.map(|l| l.count), Some(0));
1548    }
1549
1550    #[test]
1551    fn test_per_partition_limit_rejects_negative() {
1552        assert!(parse_select("SELECT * FROM ks.t PER PARTITION LIMIT -1").is_err());
1553    }
1554
1555    #[test]
1556    fn test_per_partition_limit_rejects_after_global_limit() {
1557        assert!(parse_select("SELECT * FROM ks.t LIMIT 5 PER PARTITION LIMIT 2").is_err());
1558    }
1559
1560    #[test]
1561    fn test_per_partition_limit_rejects_after_limit_offset() {
1562        // Regression (roborev job 38): the ordering guard must catch a trailing
1563        // PER PARTITION LIMIT even when LIMIT is followed by OFFSET, not just
1564        // when it immediately follows LIMIT.
1565        assert!(parse_select("SELECT * FROM ks.t LIMIT 5 OFFSET 1 PER PARTITION LIMIT 2").is_err());
1566    }
1567
1568    // --- Unquoted UUID / blob literal parser tests (Issue #956) ---
1569
1570    /// Pull the single literal out of a `WHERE <col> = <literal>` statement.
1571    fn where_equal_literal(cql: &str) -> Value {
1572        let stmt = parse_select(cql).expect("statement must parse");
1573        let where_expr = stmt.where_clause.expect("WHERE clause expected");
1574        match where_expr {
1575            WhereExpression::Comparison(ComparisonExpression {
1576                operator: ComparisonOperator::Equal,
1577                right: ComparisonRightSide::Value(SelectExpression::Literal(v)),
1578                ..
1579            }) => v,
1580            other => panic!("Expected an `= literal` comparison, got: {:?}", other),
1581        }
1582    }
1583
1584    #[test]
1585    fn test_unquoted_uuid_literal() {
1586        let v = where_equal_literal(
1587            "SELECT * FROM ks.tbl WHERE id = 550e8400-e29b-41d4-a716-446655440000",
1588        );
1589        assert_eq!(
1590            v,
1591            Value::Uuid([
1592                0x55, 0x0e, 0x84, 0x00, 0xe2, 0x9b, 0x41, 0xd4, 0xa7, 0x16, 0x44, 0x66, 0x55, 0x44,
1593                0x00, 0x00,
1594            ])
1595        );
1596    }
1597
1598    #[test]
1599    fn test_unquoted_uuid_literal_uppercase() {
1600        // UUID hex is case-insensitive; uppercase must decode to the same bytes.
1601        let lower = where_equal_literal(
1602            "SELECT * FROM ks.tbl WHERE id = 550e8400-e29b-41d4-a716-446655440000",
1603        );
1604        let upper = where_equal_literal(
1605            "SELECT * FROM ks.tbl WHERE id = 550E8400-E29B-41D4-A716-446655440000",
1606        );
1607        assert_eq!(lower, upper);
1608    }
1609
1610    #[test]
1611    fn test_uuid_starting_with_letter() {
1612        // A UUID whose first group starts with a hex *letter* must still be
1613        // recognized (it would otherwise be mis-lexed as an identifier).
1614        let v = where_equal_literal(
1615            "SELECT * FROM ks.tbl WHERE id = abcdef01-2345-6789-abcd-ef0123456789",
1616        );
1617        assert!(matches!(v, Value::Uuid(_)), "got {:?}", v);
1618    }
1619
1620    #[test]
1621    fn test_uuid_does_not_break_integer_literal() {
1622        // Regression guard: a bare integer must still tokenize as an integer,
1623        // not get swallowed by the UUID probe.
1624        let v = where_equal_literal("SELECT * FROM ks.tbl WHERE age = 5");
1625        assert_eq!(v, Value::BigInt(5));
1626    }
1627
1628    #[test]
1629    fn test_uuid_does_not_break_identifier_or_minus() {
1630        // `a716` is a plain identifier (column name), and `-` is subtraction;
1631        // neither should be misread as part of a UUID.
1632        let stmt = parse_select("SELECT a716 - 1 FROM ks.tbl").expect("must parse");
1633        assert!(matches!(stmt.select_clause, SelectClause::Columns(_)));
1634    }
1635
1636    #[test]
1637    fn test_almost_uuid_too_long_is_not_uuid() {
1638        // 33 hex digits in the last group (one extra) must NOT match a UUID.
1639        // It also isn't a valid bare token, so parsing should fail rather than
1640        // silently produce a wrong UUID.
1641        let result =
1642            parse_select("SELECT * FROM ks.tbl WHERE id = 550e8400-e29b-41d4-a716-4466554400000");
1643        // Either a parse error or a non-UUID token — the key assertion is that
1644        // it is never decoded as a (truncated) UUID literal.
1645        if let Ok(stmt) = result {
1646            if let Some(WhereExpression::Comparison(c)) = stmt.where_clause {
1647                if let ComparisonRightSide::Value(SelectExpression::Literal(v)) = c.right {
1648                    assert!(
1649                        !matches!(v, Value::Uuid(_)),
1650                        "33-hex-digit tail must not parse as a UUID, got {:?}",
1651                        v
1652                    );
1653                }
1654            }
1655        }
1656    }
1657
1658    #[test]
1659    fn test_blob_hex_literal() {
1660        let v = where_equal_literal("SELECT * FROM ks.tbl WHERE data = 0xdeadbeef");
1661        assert_eq!(v, Value::Blob(vec![0xde, 0xad, 0xbe, 0xef]));
1662    }
1663
1664    #[test]
1665    fn test_blob_hex_literal_uppercase_prefix_and_digits() {
1666        let v = where_equal_literal("SELECT * FROM ks.tbl WHERE data = 0XDEADBEEF");
1667        assert_eq!(v, Value::Blob(vec![0xde, 0xad, 0xbe, 0xef]));
1668    }
1669
1670    #[test]
1671    fn test_empty_blob_literal() {
1672        let v = where_equal_literal("SELECT * FROM ks.tbl WHERE data = 0x");
1673        assert_eq!(v, Value::Blob(vec![]));
1674    }
1675
1676    #[test]
1677    fn test_blob_odd_digit_count_errors() {
1678        // CQL blob literals require an even number of hex digits.
1679        assert!(parse_select("SELECT * FROM ks.tbl WHERE data = 0xabc").is_err());
1680    }
1681
1682    #[test]
1683    fn test_zero_integer_still_parses() {
1684        // `0` must remain an integer literal (the blob probe only fires on `0x`).
1685        let v = where_equal_literal("SELECT * FROM ks.tbl WHERE age = 0");
1686        assert_eq!(v, Value::BigInt(0));
1687    }
1688
1689    // --- Positional bind marker (`?`) parser tests (Issue #961) ---
1690
1691    #[test]
1692    fn test_single_bind_marker_in_where() {
1693        let stmt = parse_select("SELECT * FROM ks.tbl WHERE id = ?").expect("must parse");
1694        assert_eq!(stmt.bind_marker_count(), 1);
1695        let where_expr = stmt.where_clause.expect("WHERE expected");
1696        match where_expr {
1697            WhereExpression::Comparison(ComparisonExpression {
1698                operator: ComparisonOperator::Equal,
1699                right: ComparisonRightSide::Value(SelectExpression::BindMarker(idx)),
1700                ..
1701            }) => assert_eq!(idx, 0),
1702            other => panic!("expected `= ?` comparison with BindMarker(0), got {other:?}"),
1703        }
1704    }
1705
1706    #[test]
1707    fn test_multiple_bind_markers_numbered_left_to_right() {
1708        let stmt = parse_select("SELECT * FROM ks.tbl WHERE a = ? AND b = ?").expect("must parse");
1709        assert_eq!(stmt.bind_marker_count(), 2);
1710        // Collect marker indices from both comparisons in order.
1711        let WhereExpression::And(exprs) = stmt.where_clause.expect("WHERE expected") else {
1712            panic!("expected AND of two comparisons");
1713        };
1714        let indices: Vec<usize> = exprs
1715            .iter()
1716            .map(|e| match e {
1717                WhereExpression::Comparison(ComparisonExpression {
1718                    right: ComparisonRightSide::Value(SelectExpression::BindMarker(idx)),
1719                    ..
1720                }) => *idx,
1721                other => panic!("expected BindMarker comparison, got {other:?}"),
1722            })
1723            .collect();
1724        assert_eq!(indices, vec![0, 1]);
1725    }
1726
1727    #[test]
1728    fn test_bind_marker_binds_to_literal() {
1729        let mut stmt = parse_select("SELECT * FROM ks.tbl WHERE id = ?").expect("must parse");
1730        stmt.bind_parameters(&[Value::Integer(42)])
1731            .expect("binding must succeed");
1732        let where_expr = stmt.where_clause.expect("WHERE expected");
1733        match where_expr {
1734            WhereExpression::Comparison(ComparisonExpression {
1735                right: ComparisonRightSide::Value(SelectExpression::Literal(v)),
1736                ..
1737            }) => assert_eq!(v, Value::Integer(42)),
1738            other => panic!("expected bound literal, got {other:?}"),
1739        }
1740    }
1741
1742    #[test]
1743    fn test_bind_marker_in_list_count() {
1744        let stmt = parse_select("SELECT * FROM ks.tbl WHERE id IN (?, ?, ?)").expect("must parse");
1745        assert_eq!(stmt.bind_marker_count(), 3);
1746    }
1747
1748    #[test]
1749    fn test_bind_marker_count_mismatch_errors() {
1750        let mut stmt = parse_select("SELECT * FROM ks.tbl WHERE id = ?").expect("must parse");
1751        assert!(stmt.bind_parameters(&[]).is_err(), "too few must error");
1752        let mut stmt2 = parse_select("SELECT * FROM ks.tbl WHERE id = ?").expect("must parse");
1753        assert!(
1754            stmt2
1755                .bind_parameters(&[Value::Integer(1), Value::Integer(2)])
1756                .is_err(),
1757            "too many must error"
1758        );
1759    }
1760
1761    #[test]
1762    fn test_no_bind_markers_count_zero() {
1763        let stmt = parse_select("SELECT * FROM ks.tbl WHERE id = 5").expect("must parse");
1764        assert_eq!(stmt.bind_marker_count(), 0);
1765    }
1766
1767    #[test]
1768    fn test_in_clause() {
1769        let stmt =
1770            parse_select("SELECT * FROM users WHERE status IN ('active', 'pending', 'suspended')")
1771                .unwrap();
1772        assert!(stmt.where_clause.is_some());
1773    }
1774
1775    #[test]
1776    fn test_between_clause() {
1777        let stmt = parse_select(
1778            "SELECT * FROM events WHERE created_at BETWEEN '2024-01-01' AND '2024-12-31'",
1779        )
1780        .unwrap();
1781        assert!(stmt.where_clause.is_some());
1782    }
1783}