Skip to main content

radixdb_sql/ast/
query.rs

1// Copyright 2026 RadixDB Contributors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use super::*;
16
17// ============================================================================
18// WITH Clause (CTEs)
19// ============================================================================
20
21/// Common Table Expression
22#[derive(Debug, Clone, PartialEq)]
23pub struct CommonTableExpression {
24    pub token: Token,
25    pub name: Identifier,
26    pub column_names: Vec<Identifier>,
27    pub query: Box<SelectStatement>,
28    pub is_recursive: bool,
29}
30
31impl fmt::Display for CommonTableExpression {
32    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
33        let mut result = self.name.to_string();
34        if !self.column_names.is_empty() {
35            let cols: Vec<String> = self.column_names.iter().map(|c| c.to_string()).collect();
36            result.push_str(&format!("({})", cols.join(", ")));
37        }
38        result.push_str(&format!(" AS ({})", self.query));
39        write!(f, "{}", result)
40    }
41}
42
43/// WITH clause
44#[derive(Debug, Clone, PartialEq)]
45pub struct WithClause {
46    pub token: Token,
47    pub ctes: Vec<CommonTableExpression>,
48    pub is_recursive: bool,
49}
50
51impl fmt::Display for WithClause {
52    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
53        let mut result = String::from("WITH ");
54        if self.is_recursive {
55            result.push_str("RECURSIVE ");
56        }
57        let cte_strs: Vec<String> = self.ctes.iter().map(|c| c.to_string()).collect();
58        result.push_str(&cte_strs.join(", "));
59        write!(f, "{}", result)
60    }
61}
62
63// ============================================================================
64// Statement Types
65// ============================================================================
66
67/// Set operation type for compound queries
68#[derive(Debug, Clone, PartialEq)]
69pub enum SetOperationType {
70    Union,
71    UnionAll,
72    Intersect,
73    IntersectAll,
74    Except,
75    ExceptAll,
76}
77
78impl fmt::Display for SetOperationType {
79    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
80        match self {
81            SetOperationType::Union => write!(f, "UNION"),
82            SetOperationType::UnionAll => write!(f, "UNION ALL"),
83            SetOperationType::Intersect => write!(f, "INTERSECT"),
84            SetOperationType::IntersectAll => write!(f, "INTERSECT ALL"),
85            SetOperationType::Except => write!(f, "EXCEPT"),
86            SetOperationType::ExceptAll => write!(f, "EXCEPT ALL"),
87        }
88    }
89}
90
91/// Set operation combining two SELECT statements
92#[derive(Debug, Clone, PartialEq)]
93pub struct SetOperation {
94    pub operation: SetOperationType,
95    pub right: Box<SelectStatement>,
96}
97
98/// Group by modifier (ROLLUP, CUBE, GROUPING SETS, or none)
99#[derive(Debug, Clone, PartialEq, Default)]
100pub enum GroupByModifier {
101    #[default]
102    None,
103    Rollup,
104    Cube,
105    /// GROUPING SETS - each inner Vec is one grouping set
106    /// e.g., GROUPING SETS ((a, b), (a), ()) has 3 sets
107    GroupingSets(Vec<Vec<Expression>>),
108}
109
110/// GROUP BY clause with optional ROLLUP/CUBE modifier
111#[derive(Debug, Clone, PartialEq, Default)]
112pub struct GroupByClause {
113    pub columns: Vec<Expression>,
114    pub modifier: GroupByModifier,
115}
116
117impl fmt::Display for GroupByClause {
118    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
119        // GROUPING SETS uses its own column lists, not self.columns
120        if let GroupByModifier::GroupingSets(sets) = &self.modifier {
121            let sets_str: Vec<String> = sets
122                .iter()
123                .map(|set| {
124                    let cols: Vec<String> = set.iter().map(|c| c.to_string()).collect();
125                    format!("({})", cols.join(", "))
126                })
127                .collect();
128            return write!(f, "GROUPING SETS ({})", sets_str.join(", "));
129        }
130
131        // None, Rollup, Cube all use self.columns
132        if self.columns.is_empty() {
133            return Ok(());
134        }
135        let cols: Vec<String> = self.columns.iter().map(|c| c.to_string()).collect();
136        match &self.modifier {
137            GroupByModifier::None => write!(f, "{}", cols.join(", ")),
138            GroupByModifier::Rollup => write!(f, "ROLLUP({})", cols.join(", ")),
139            GroupByModifier::Cube => write!(f, "CUBE({})", cols.join(", ")),
140            GroupByModifier::GroupingSets(_) => Ok(()), // Already handled above
141        }
142    }
143}
144
145/// SELECT statement
146#[derive(Debug, Clone, PartialEq)]
147pub struct SelectStatement {
148    pub token: Token,
149    pub distinct: bool,
150    /// DISTINCT ON (expr1, expr2, ...) expressions. Empty for regular DISTINCT or no DISTINCT.
151    pub distinct_on: Vec<Expression>,
152    pub columns: Vec<Expression>,
153    pub with: Option<WithClause>,
154    pub table_expr: Option<Box<Expression>>,
155    pub where_clause: Option<Box<Expression>>,
156    pub group_by: GroupByClause,
157    pub having: Option<Box<Expression>>,
158    /// Named window definitions (WINDOW w AS (...))
159    pub window_defs: Vec<WindowDefinition>,
160    pub order_by: Vec<OrderByExpression>,
161    pub limit: Option<Box<Expression>>,
162    pub offset: Option<Box<Expression>>,
163    /// Set operations (UNION, INTERSECT, EXCEPT)
164    pub set_operations: Vec<SetOperation>,
165}
166
167impl fmt::Display for SelectStatement {
168    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
169        let mut result = String::new();
170        if let Some(ref with) = self.with {
171            result.push_str(&format!("{} ", with));
172        }
173        result.push_str("SELECT ");
174        if !self.distinct_on.is_empty() {
175            let on_cols: Vec<String> = self.distinct_on.iter().map(|e| e.to_string()).collect();
176            result.push_str(&format!("DISTINCT ON ({}) ", on_cols.join(", ")));
177        } else if self.distinct {
178            result.push_str("DISTINCT ");
179        }
180        let cols: Vec<String> = self.columns.iter().map(|c| c.to_string()).collect();
181        result.push_str(&cols.join(", "));
182        if let Some(ref table) = self.table_expr {
183            result.push_str(&format!(" FROM {}", table));
184        }
185        if let Some(ref where_clause) = self.where_clause {
186            result.push_str(&format!(" WHERE {}", where_clause));
187        }
188        if !self.group_by.columns.is_empty()
189            || matches!(self.group_by.modifier, GroupByModifier::GroupingSets(_))
190        {
191            result.push_str(&format!(" GROUP BY {}", self.group_by));
192        }
193        if let Some(ref having) = self.having {
194            result.push_str(&format!(" HAVING {}", having));
195        }
196        if !self.window_defs.is_empty() {
197            let wins: Vec<String> = self.window_defs.iter().map(|w| w.to_string()).collect();
198            result.push_str(&format!(" WINDOW {}", wins.join(", ")));
199        }
200        // Set-operation branches bind before the outer ORDER/LIMIT/OFFSET.
201        for set_op in &self.set_operations {
202            result.push_str(&format!(" {} {}", set_op.operation, set_op.right));
203        }
204        if !self.order_by.is_empty() {
205            let orders: Vec<String> = self.order_by.iter().map(|o| o.to_string()).collect();
206            result.push_str(&format!(" ORDER BY {}", orders.join(", ")));
207        }
208        if let Some(ref limit) = self.limit {
209            result.push_str(&format!(" LIMIT {}", limit));
210        }
211        if let Some(ref offset) = self.offset {
212            result.push_str(&format!(" OFFSET {}", offset));
213        }
214        write!(f, "{}", result)
215    }
216}