risingwave_sqlsmith/sqlreduce/passes/
remove.rs

1// Copyright 2025 RisingWave Labs
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 risingwave_sqlparser::ast::SetExpr;
16
17use crate::sqlreduce::passes::{Ast, Transform, extract_query, extract_query_mut};
18
19/// Remove individual expressions from the GROUP BY clause.
20///
21/// Example:
22/// ```sql
23/// SELECT a, COUNT(*) FROM t GROUP BY a, b;
24/// ```
25/// May be reduced to:
26/// ```sql
27/// SELECT a, COUNT(*) FROM t GROUP BY b;
28/// ```
29pub struct GroupByRemove;
30
31impl Transform for GroupByRemove {
32    fn name(&self) -> String {
33        "groupby_remove".to_owned()
34    }
35
36    fn get_reduction_points(&self, ast: Ast) -> Vec<usize> {
37        let mut reduction_points = Vec::new();
38        if let Some(query) = extract_query(&ast)
39            && let SetExpr::Select(select) = &query.body
40        {
41            for i in 0..select.group_by.len() {
42                reduction_points.push(i);
43            }
44        }
45        reduction_points.reverse();
46        reduction_points
47    }
48
49    fn apply_on(&self, ast: &mut Ast, reduction_points: Vec<usize>) -> Ast {
50        if let Some(query) = extract_query_mut(ast)
51            && let SetExpr::Select(select) = &mut query.body
52        {
53            for i in reduction_points {
54                select.group_by.remove(i);
55            }
56        }
57        ast.clone()
58    }
59}
60
61/// Remove individual items from the ORDER BY clause.
62///
63/// Example:
64/// ```sql
65/// SELECT * FROM t ORDER BY a, b;
66/// ```
67/// May be reduced to:
68/// ```sql
69/// SELECT * FROM t ORDER BY b;
70/// ```
71pub struct OrderByRemove;
72
73impl Transform for OrderByRemove {
74    fn name(&self) -> String {
75        "orderby_remove".to_owned()
76    }
77
78    fn get_reduction_points(&self, ast: Ast) -> Vec<usize> {
79        let mut reduction_points = Vec::new();
80        if let Some(query) = extract_query(&ast) {
81            for i in 0..query.order_by.len() {
82                reduction_points.push(i);
83            }
84        }
85        reduction_points.reverse();
86        reduction_points
87    }
88
89    fn apply_on(&self, ast: &mut Ast, reduction_points: Vec<usize>) -> Ast {
90        if let Some(query) = extract_query_mut(ast) {
91            for i in reduction_points {
92                query.order_by.remove(i);
93            }
94        }
95        ast.clone()
96    }
97}
98
99/// Remove the entire WHERE clause from the query.
100///
101/// Example:
102/// ```sql
103/// SELECT * FROM t WHERE a > 1;
104/// ```
105/// Will be reduced to:
106/// ```sql
107/// SELECT * FROM t;
108/// ```
109pub struct WhereRemove;
110
111impl Transform for WhereRemove {
112    fn name(&self) -> String {
113        "where_remove".to_owned()
114    }
115
116    fn get_reduction_points(&self, ast: Ast) -> Vec<usize> {
117        let mut reduction_points = Vec::new();
118        if let Some(query) = extract_query(&ast)
119            && let SetExpr::Select(select) = &query.body
120            && select.selection.is_some()
121        {
122            reduction_points.push(0);
123        }
124        reduction_points.reverse();
125        reduction_points
126    }
127
128    fn apply_on(&self, ast: &mut Ast, reduction_points: Vec<usize>) -> Ast {
129        if let Some(query) = extract_query_mut(ast)
130            && let SetExpr::Select(select) = &mut query.body
131        {
132            for _ in reduction_points {
133                select.selection = None;
134            }
135        }
136        tracing::info!("where_remove ast: {:?}", ast);
137        ast.clone()
138    }
139}
140
141/// Remove individual table sources from the FROM clause.
142///
143/// Example:
144/// ```sql
145/// SELECT * FROM t1, t2;
146/// ```
147/// May be reduced to:
148/// ```sql
149/// SELECT * FROM t2;
150/// ```
151pub struct FromRemove;
152
153impl Transform for FromRemove {
154    fn name(&self) -> String {
155        "from_remove".to_owned()
156    }
157
158    fn get_reduction_points(&self, ast: Ast) -> Vec<usize> {
159        let mut reduction_points = Vec::new();
160        if let Some(query) = extract_query(&ast)
161            && let SetExpr::Select(select) = &query.body
162        {
163            for i in 0..select.from.len() {
164                reduction_points.push(i);
165            }
166        }
167        reduction_points.reverse();
168        reduction_points
169    }
170
171    fn apply_on(&self, ast: &mut Ast, reduction_points: Vec<usize>) -> Ast {
172        if let Some(query) = extract_query_mut(ast)
173            && let SetExpr::Select(select) = &mut query.body
174        {
175            for i in reduction_points {
176                select.from.remove(i);
177            }
178        }
179        ast.clone()
180    }
181}
182
183/// Remove individual items from the SELECT list.
184///
185/// Example:
186/// ```sql
187/// SELECT a, b, c FROM t;
188/// ```
189/// May be reduced to:
190/// ```sql
191/// SELECT a, c FROM t;
192/// ```
193pub struct SelectItemRemove;
194
195impl Transform for SelectItemRemove {
196    fn name(&self) -> String {
197        "select_item_remove".to_owned()
198    }
199
200    fn get_reduction_points(&self, ast: Ast) -> Vec<usize> {
201        let mut reduction_points = Vec::new();
202        if let Some(query) = extract_query(&ast)
203            && let SetExpr::Select(select) = &query.body
204        {
205            for i in 0..select.projection.len() {
206                reduction_points.push(i);
207            }
208        }
209        reduction_points.reverse();
210        reduction_points
211    }
212
213    fn apply_on(&self, ast: &mut Ast, reduction_points: Vec<usize>) -> Ast {
214        if let Some(query) = extract_query_mut(ast)
215            && let SetExpr::Select(select) = &mut query.body
216        {
217            for i in reduction_points {
218                select.projection.remove(i);
219            }
220        }
221        ast.clone()
222    }
223}
224
225pub struct HavingRemove;
226
227impl Transform for HavingRemove {
228    fn name(&self) -> String {
229        "having_remove".to_owned()
230    }
231
232    fn get_reduction_points(&self, ast: Ast) -> Vec<usize> {
233        let mut reduction_points = Vec::new();
234        if let Some(query) = extract_query(&ast)
235            && let SetExpr::Select(select) = &query.body
236            && select.having.is_some()
237        {
238            reduction_points.push(0);
239        }
240
241        reduction_points.reverse();
242        reduction_points
243    }
244
245    fn apply_on(&self, ast: &mut Ast, reduction_points: Vec<usize>) -> Ast {
246        if let Some(query) = extract_query_mut(ast)
247            && let SetExpr::Select(select) = &mut query.body
248        {
249            for _ in reduction_points {
250                select.having = None;
251            }
252        }
253        ast.clone()
254    }
255}
256
257#[cfg(test)]
258mod tests {
259    use super::*;
260    use crate::parse_sql;
261
262    #[test]
263    fn test_groupby_remove_with_single_strategy() {
264        let sql = "SELECT a, COUNT(*) FROM t GROUP BY a, b, c;";
265        let ast = parse_sql(sql);
266        let reduction_points = GroupByRemove.get_reduction_points(ast[0].clone());
267        assert_eq!(reduction_points, vec![2, 1, 0]);
268
269        let new_ast = GroupByRemove.apply_on(&mut ast[0].clone(), reduction_points[..1].to_vec());
270        assert_eq!(
271            new_ast,
272            parse_sql("SELECT a, COUNT(*) FROM t GROUP BY a, b;")[0].clone()
273        );
274    }
275
276    #[test]
277    fn test_groupby_remove_with_multiple_strategy() {
278        let sql = "SELECT a, COUNT(*) FROM t GROUP BY a, b, c;";
279        let ast = parse_sql(sql);
280        let reduction_points = GroupByRemove.get_reduction_points(ast[0].clone());
281        assert_eq!(reduction_points, vec![2, 1, 0]);
282
283        let new_ast = GroupByRemove.apply_on(&mut ast[0].clone(), reduction_points);
284        assert_eq!(new_ast, parse_sql("SELECT a, COUNT(*) FROM t;")[0].clone());
285    }
286
287    #[test]
288    fn test_orderby_remove_with_single_strategy() {
289        let sql = "SELECT a, b FROM t ORDER BY a, b, c;";
290        let ast = parse_sql(sql);
291        let reduction_points = OrderByRemove.get_reduction_points(ast[0].clone());
292        assert_eq!(reduction_points, vec![2, 1, 0]);
293
294        let new_ast = OrderByRemove.apply_on(&mut ast[0].clone(), reduction_points[..1].to_vec());
295        assert_eq!(
296            new_ast,
297            parse_sql("SELECT a, b FROM t ORDER BY a, b;")[0].clone()
298        );
299    }
300
301    #[test]
302    fn test_orderby_remove_with_multiple_strategy() {
303        let sql = "SELECT a, b FROM t ORDER BY a, b, c;";
304        let ast = parse_sql(sql);
305        let reduction_points = OrderByRemove.get_reduction_points(ast[0].clone());
306        assert_eq!(reduction_points, vec![2, 1, 0]);
307
308        let new_ast = OrderByRemove.apply_on(&mut ast[0].clone(), reduction_points);
309        assert_eq!(new_ast, parse_sql("SELECT a, b FROM t;")[0].clone());
310    }
311
312    #[test]
313    fn test_where_remove() {
314        let sql = "SELECT * FROM t WHERE a > 1;";
315        let ast = parse_sql(sql);
316        let reduction_points = WhereRemove.get_reduction_points(ast[0].clone());
317        assert_eq!(reduction_points, vec![0]);
318
319        let new_ast = WhereRemove.apply_on(&mut ast[0].clone(), reduction_points);
320        assert_eq!(new_ast, parse_sql("SELECT * FROM t;")[0].clone());
321    }
322
323    #[test]
324    fn test_from_remove_with_single_strategy() {
325        let sql = "SELECT * FROM t1, t2;";
326        let ast = parse_sql(sql);
327        let reduction_points = FromRemove.get_reduction_points(ast[0].clone());
328        assert_eq!(reduction_points, vec![1, 0]);
329
330        let new_ast = FromRemove.apply_on(&mut ast[0].clone(), reduction_points[..1].to_vec());
331        assert_eq!(new_ast, parse_sql("SELECT * FROM t1;")[0].clone());
332    }
333
334    #[test]
335    fn test_from_remove_with_multiple_strategy() {
336        let sql = "SELECT * FROM t1, t2;";
337        let ast = parse_sql(sql);
338        let reduction_points = FromRemove.get_reduction_points(ast[0].clone());
339        assert_eq!(reduction_points, vec![1, 0]);
340
341        let new_ast = FromRemove.apply_on(&mut ast[0].clone(), reduction_points);
342        assert_eq!(new_ast, parse_sql("SELECT *;")[0].clone());
343    }
344
345    #[test]
346    fn test_select_item_remove() {
347        let sql = "SELECT a, b, c FROM t;";
348        let ast = parse_sql(sql);
349        let reduction_points = SelectItemRemove.get_reduction_points(ast[0].clone());
350        assert_eq!(reduction_points, vec![2, 1, 0]);
351
352        let new_ast =
353            SelectItemRemove.apply_on(&mut ast[0].clone(), reduction_points[..1].to_vec());
354        assert_eq!(new_ast, parse_sql("SELECT a, b FROM t;")[0].clone());
355    }
356
357    #[test]
358    fn test_having_remove() {
359        let sql = "SELECT * FROM t HAVING a > 1;";
360        let ast = parse_sql(sql);
361        let reduction_points = HavingRemove.get_reduction_points(ast[0].clone());
362        assert_eq!(reduction_points, vec![0]);
363
364        let new_ast = HavingRemove.apply_on(&mut ast[0].clone(), reduction_points);
365        assert_eq!(new_ast, parse_sql("SELECT * FROM t;")[0].clone());
366    }
367}