Skip to main content

reddb_server/storage/query/parser/
cte.rs

1//! CTE (Common Table Expression) parsing
2
3use super::super::ast::{CteDefinition, QueryExpr, QueryWithCte, WithClause};
4use super::super::lexer::Token;
5use super::error::{ParseError, SafeTokenDisplay};
6use super::Parser;
7
8impl<'a> Parser<'a> {
9    /// Parse a complete query with optional WITH clause
10    pub fn parse_with_cte(&mut self) -> Result<QueryWithCte, ParseError> {
11        // Check for WITH clause
12        if self.check(&Token::With) && !self.is_with_filter_context() {
13            let with_clause = self.parse_with_clause()?;
14            let query = self.parse_query_expr()?;
15
16            // Expect end of input
17            if !self.check(&Token::Eof) {
18                return Err(ParseError::new(
19                    // F-05: route the offending token through
20                    // `SafeTokenDisplay` so user-controlled `Ident` /
21                    // `String` / `JsonLiteral` payloads are escaped before
22                    // the message reaches downstream serialization sinks.
23                    format!(
24                        "Unexpected token after query: {}",
25                        SafeTokenDisplay(&self.current.token)
26                    ),
27                    self.position(),
28                ));
29            }
30
31            Ok(QueryWithCte::with_ctes(with_clause, query))
32        } else {
33            let query = self.parse_query_expr()?;
34
35            // Expect end of input
36            if !self.check(&Token::Eof) {
37                return Err(ParseError::new(
38                    // F-05: same rationale as the CTE arm above.
39                    format!(
40                        "Unexpected token after query: {}",
41                        SafeTokenDisplay(&self.current.token)
42                    ),
43                    self.position(),
44                ));
45            }
46
47            Ok(QueryWithCte::simple(query))
48        }
49    }
50
51    /// Check if WITH is part of a filter (STARTS WITH, ENDS WITH)
52    fn is_with_filter_context(&self) -> bool {
53        // WITH at start of query is CTE, not filter
54        false
55    }
56
57    /// Parse WITH clause containing one or more CTEs
58    ///
59    /// Syntax:
60    /// ```text
61    /// WITH [RECURSIVE] cte_name [(columns)] AS (query) [, ...]
62    /// ```
63    fn parse_with_clause(&mut self) -> Result<WithClause, ParseError> {
64        self.expect(Token::With)?;
65
66        // Check for RECURSIVE keyword
67        let is_recursive = self.consume(&Token::Recursive)?;
68
69        let mut with_clause = WithClause::new();
70
71        // Parse CTEs (comma-separated)
72        loop {
73            let cte = self.parse_cte_definition(is_recursive)?;
74            with_clause = with_clause.add(cte);
75
76            // Check for more CTEs
77            if !self.consume(&Token::Comma)? {
78                break;
79            }
80        }
81
82        Ok(with_clause)
83    }
84
85    /// Parse a single CTE definition
86    ///
87    /// Syntax:
88    /// ```text
89    /// cte_name [(column1, column2, ...)] AS (query)
90    /// ```
91    fn parse_cte_definition(&mut self, is_recursive: bool) -> Result<CteDefinition, ParseError> {
92        // CTE name
93        let name = self.expect_ident()?;
94
95        // Optional column list
96        let columns = if self.consume(&Token::LParen)? {
97            let mut cols = Vec::new();
98            loop {
99                cols.push(self.expect_ident()?);
100                if !self.consume(&Token::Comma)? {
101                    break;
102                }
103            }
104            self.expect(Token::RParen)?;
105            cols
106        } else {
107            Vec::new()
108        };
109
110        // AS keyword
111        self.expect(Token::As)?;
112
113        // CTE query in parentheses
114        self.expect(Token::LParen)?;
115        let query = self.parse_cte_query()?;
116        self.expect(Token::RParen)?;
117
118        let mut cte = if is_recursive {
119            CteDefinition::recursive(&name, query)
120        } else {
121            CteDefinition::new(&name, query)
122        };
123
124        if !columns.is_empty() {
125            cte = cte.with_columns(columns);
126        }
127
128        Ok(cte)
129    }
130
131    /// Parse a query within a CTE (supports UNION ALL for recursive CTEs)
132    fn parse_cte_query(&mut self) -> Result<QueryExpr, ParseError> {
133        // Parse the base query
134        let query = self.parse_query_expr()?;
135
136        // For recursive CTEs, we might have UNION ALL
137        // For now, we just return the base query
138        // Full UNION ALL support would require a UnionQuery variant
139        if self.check(&Token::Union) {
140            // Skip UNION ALL - the recursive part references the CTE by name
141            // Full implementation would parse both sides
142            self.advance()?;
143            if self.consume(&Token::All)? {
144                // Parse the recursive part
145                let _recursive_query = self.parse_query_expr()?;
146                // For now, return just the base - execution handles recursion
147            }
148        }
149
150        Ok(query)
151    }
152}