1use crate::sql::parser::ast::{
2 CTEType, SelectItem, SelectStatement, SqlExpression, WhereClause, CTE,
3};
4use crate::sql::parser::walk;
5use std::collections::{HashMap, HashSet};
6
7pub struct CTEHoister {
26 hoisted_ctes: Vec<CTE>,
27 _cte_counter: usize,
28 dependency_graph: HashMap<String, HashSet<String>>,
29}
30
31impl CTEHoister {
32 pub fn new() -> Self {
33 Self {
34 hoisted_ctes: Vec::new(),
35 _cte_counter: 0,
36 dependency_graph: HashMap::new(),
37 }
38 }
39
40 pub fn hoist_ctes(mut statement: SelectStatement) -> SelectStatement {
42 let mut hoister = CTEHoister::new();
43
44 for cte in statement.ctes.drain(..) {
46 hoister.add_cte(cte);
47 }
48
49 let rewritten = hoister.hoist_from_statement(statement);
51
52 SelectStatement {
54 ctes: hoister.get_ordered_ctes(),
55 ..rewritten
56 }
57 }
58
59 fn hoist_from_statement(&mut self, mut statement: SelectStatement) -> SelectStatement {
61 statement.map_from_subquery(|subquery| {
63 let rewritten_sub = self.hoist_from_statement(subquery);
64
65 for cte in rewritten_sub.ctes.clone() {
67 self.add_cte(cte);
68 }
69
70 SelectStatement {
72 ctes: Vec::new(),
73 ..rewritten_sub
74 }
75 });
76
77 let local_ctes = statement.ctes.drain(..).collect::<Vec<_>>();
79 for mut cte in local_ctes {
80 if let CTEType::Standard(query) = cte.cte_type {
82 let hoisted_query = self.hoist_from_statement(query);
83 cte.cte_type = CTEType::Standard(hoisted_query);
84 }
85 self.add_cte(cte);
87 }
88
89 statement.select_items = statement
91 .select_items
92 .into_iter()
93 .map(|item| self.hoist_from_select_item(item))
94 .collect();
95
96 if let Some(where_clause) = &mut statement.where_clause {
98 self.hoist_from_where_clause(where_clause);
99 }
100
101 SelectStatement {
103 ctes: Vec::new(),
104 ..statement
105 }
106 }
107
108 fn hoist_from_select_item(&mut self, item: SelectItem) -> SelectItem {
110 match item {
111 SelectItem::Expression {
112 expr,
113 alias,
114 leading_comments,
115 trailing_comment,
116 } => SelectItem::Expression {
117 expr: self.hoist_from_expression(expr),
118 alias,
119 leading_comments,
120 trailing_comment,
121 },
122 other => other,
123 }
124 }
125
126 fn hoist_from_expression(&mut self, expr: SqlExpression) -> SqlExpression {
137 walk::map_children_crossing(
138 expr,
139 self,
140 |h, e| h.hoist_from_expression(e),
141 |h, stmt| Box::new(h.hoist_from_statement(*stmt)),
142 )
143 }
144
145 fn hoist_from_where_clause(&mut self, where_clause: &mut WhereClause) {
147 for condition in &mut where_clause.conditions {
148 condition.expr = self.hoist_from_expression(condition.expr.clone());
149 }
150 }
151
152 fn add_cte(&mut self, cte: CTE) {
154 self.analyze_cte_dependencies(&cte);
156 self.hoisted_ctes.push(cte);
157 }
158
159 fn analyze_cte_dependencies(&mut self, cte: &CTE) {
161 let mut deps = HashSet::new();
162 if let CTEType::Standard(query) = &cte.cte_type {
163 self.find_cte_references(query, &mut deps);
164 }
165 self.dependency_graph.insert(cte.name.clone(), deps);
166 }
167
168 fn find_cte_references(&self, statement: &SelectStatement, deps: &mut HashSet<String>) {
170 if let Some(table) = &statement.from_table {
172 for cte in &self.hoisted_ctes {
174 if cte.name == *table {
175 deps.insert(table.clone());
176 }
177 }
178 }
179
180 if let Some(subquery) = &statement.from_subquery {
182 self.find_cte_references(subquery, deps);
183 }
184
185 for join in &statement.joins {
187 if let crate::sql::parser::ast::TableSource::Table(table_name) = &join.table {
189 for cte in &self.hoisted_ctes {
190 if cte.name == *table_name {
191 deps.insert(table_name.clone());
192 }
193 }
194 }
195 }
196
197 for item in &statement.select_items {
199 if let SelectItem::Expression { expr, .. } = item {
200 self.find_cte_refs_in_expression(expr, deps);
201 }
202 }
203
204 if let Some(where_clause) = &statement.where_clause {
206 for condition in &where_clause.conditions {
207 self.find_cte_refs_in_expression(&condition.expr, deps);
208 }
209 }
210 }
211
212 fn find_cte_refs_in_expression(&self, expr: &SqlExpression, deps: &mut HashSet<String>) {
219 walk::visit_children_crossing(
220 expr,
221 deps,
222 |deps, child| self.find_cte_refs_in_expression(child, deps),
223 |deps, stmt| self.find_cte_references(stmt, deps),
224 )
225 }
226
227 fn get_ordered_ctes(self) -> Vec<CTE> {
229 let mut result = Vec::new();
231 let mut visited = HashSet::new();
232 let mut temp_mark = HashSet::new();
233
234 fn visit(
235 name: &str,
236 graph: &HashMap<String, HashSet<String>>,
237 ctes: &[CTE],
238 visited: &mut HashSet<String>,
239 temp_mark: &mut HashSet<String>,
240 result: &mut Vec<CTE>,
241 ) {
242 if visited.contains(name) {
243 return;
244 }
245 if temp_mark.contains(name) {
246 return;
248 }
249
250 temp_mark.insert(name.to_string());
251
252 if let Some(deps) = graph.get(name) {
253 for dep in deps {
254 visit(dep, graph, ctes, visited, temp_mark, result);
255 }
256 }
257
258 temp_mark.remove(name);
259 visited.insert(name.to_string());
260
261 if let Some(cte) = ctes.iter().find(|c| c.name == name) {
263 result.push(cte.clone());
264 }
265 }
266
267 for cte in &self.hoisted_ctes {
269 visit(
270 &cte.name,
271 &self.dependency_graph,
272 &self.hoisted_ctes,
273 &mut visited,
274 &mut temp_mark,
275 &mut result,
276 );
277 }
278
279 result
280 }
281}
282
283#[cfg(test)]
284mod tests {
285 use super::*;
286
287 #[test]
300 fn test_derived_table_hoisting_updates_from_source() {
301 use crate::sql::parser::ast::TableSource;
302 use crate::sql::recursive_parser::Parser;
303
304 let mut parser = Parser::new(
305 "SELECT symbol FROM (WITH x AS (SELECT symbol FROM trades) SELECT symbol FROM x) sub",
306 );
307 let stmt = parser.parse().expect("query should parse");
308
309 let hoisted = CTEHoister::hoist_ctes(stmt);
310
311 assert_eq!(hoisted.ctes.len(), 1, "inner CTE should be hoisted");
313 assert_eq!(hoisted.ctes[0].name, "x");
314
315 #[allow(deprecated)]
318 let legacy = hoisted
319 .from_subquery
320 .as_ref()
321 .expect("from_subquery should be present");
322 assert!(legacy.ctes.is_empty(), "legacy copy should be stripped");
323
324 match hoisted.from_source {
325 Some(TableSource::DerivedTable {
326 ref query,
327 ref alias,
328 }) => {
329 assert!(
330 query.ctes.is_empty(),
331 "from_source holds a stale pre-hoist subquery with {} CTE(s)",
332 query.ctes.len()
333 );
334 assert_eq!(alias, "sub", "derived-table alias must survive the rewrite");
335 }
336 ref other => panic!("expected a DerivedTable from_source, got {other:?}"),
337 }
338 }
339
340 #[test]
341 fn test_simple_cte_hoisting() {
342 let inner_query = SelectStatement {
344 distinct: false,
345 columns: vec!["col1".to_string()],
346 select_items: vec![],
347 from_source: None,
348 #[allow(deprecated)]
349 from_table: Some("table1".to_string()),
350 #[allow(deprecated)]
351 from_subquery: None,
352 #[allow(deprecated)]
353 from_function: None,
354 #[allow(deprecated)]
355 from_alias: None,
356 joins: vec![],
357 where_clause: None,
358 order_by: None,
359 group_by: None,
360 having: None,
361 qualify: None,
362 limit: None,
363 offset: None,
364 ctes: vec![],
365 into_table: None,
366 set_operations: vec![],
367 leading_comments: vec![],
368 trailing_comment: None,
369 };
370
371 let nested_query = SelectStatement {
372 distinct: false,
373 columns: vec![],
374 select_items: vec![],
375 from_source: None,
376 #[allow(deprecated)]
377 from_subquery: Some(Box::new(SelectStatement {
378 distinct: false,
379 columns: vec![],
380 select_items: vec![],
381 ctes: vec![CTE {
382 name: "inner".to_string(),
383 column_list: None,
384 cte_type: CTEType::Standard(inner_query),
385 }],
386 from_source: None,
387 #[allow(deprecated)]
388 from_table: Some("inner".to_string()),
389 #[allow(deprecated)]
390 from_subquery: None,
391 #[allow(deprecated)]
392 from_function: None,
393 #[allow(deprecated)]
394 from_alias: None,
395 joins: vec![],
396 where_clause: None,
397 order_by: None,
398 group_by: None,
399 having: None,
400 qualify: None,
401 limit: None,
402 offset: None,
403 into_table: None,
404 set_operations: vec![],
405 leading_comments: vec![],
406 trailing_comment: None,
407 })),
408 #[allow(deprecated)]
409 from_table: None,
410 #[allow(deprecated)]
411 from_function: None,
412 #[allow(deprecated)]
413 from_alias: None,
414 joins: vec![],
415 where_clause: None,
416 order_by: None,
417 group_by: None,
418 having: None,
419 qualify: None,
420 limit: None,
421 offset: None,
422 ctes: vec![],
423 into_table: None,
424 set_operations: vec![],
425 leading_comments: vec![],
426 trailing_comment: None,
427 };
428
429 let result = CTEHoister::hoist_ctes(nested_query);
430
431 assert_eq!(result.ctes.len(), 1);
432 assert_eq!(result.ctes[0].name, "inner");
433 assert!(result.from_subquery.as_ref().unwrap().ctes.is_empty());
434 }
435}