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}