Normalize the way subqueries appear in [`HirScalarExpr::Exists`] or [`HirScalarExpr::Select`] variants. After the transform is applied, subqueries are pulled as a value in a let binding enclosing the [`HirRelationExpr`] parent of the [`HirScalarExpr::Exists`] or [`HirScalarExpr::Select`] where the subquery appears, and the corresponding variant references the new binding with a [`HirRelationExpr:
(expr: &'a mut HirRelationExpr)
| 81 | /// subquery appears, and the corresponding variant references the |
| 82 | /// new binding with a [`HirRelationExpr::Get`]. |
| 83 | pub fn normalize_subqueries<'a>(expr: &'a mut HirRelationExpr) { |
| 84 | // A helper struct to represent accumulated `$local_id = $subquery` |
| 85 | // bindings that need to be installed in `let ... in $expr` nodes |
| 86 | // that wrap their parent $expr. |
| 87 | struct Binding { |
| 88 | local_id: LocalId, |
| 89 | subquery: HirRelationExpr, |
| 90 | } |
| 91 | |
| 92 | // Context for the transformation |
| 93 | // - a stack of bindings |
| 94 | let mut bindings = Vec::<Binding>::new(); |
| 95 | // - a generator of fresh local ids |
| 96 | let mut id_gen = id_gen(expr).peekable(); |
| 97 | |
| 98 | // Grow the `bindings` stack by collecting subqueries appearing in |
| 99 | // one of the HirScalarExpr children at the given HirRelationExpr. |
| 100 | // As part of this, the subquery is replaced by a `Get(id)` for a |
| 101 | // fresh local id. |
| 102 | let mut collect_subqueries = |expr: &mut HirRelationExpr, bindings: &mut Vec<Binding>| { |
| 103 | expr.visit_mut_children(|expr: &mut HirScalarExpr| { |
| 104 | use HirRelationExpr::Get; |
| 105 | use HirScalarExpr::{Exists, Select}; |
| 106 | expr.visit_mut_post(&mut |expr: &mut HirScalarExpr| match expr { |
| 107 | Exists(expr, _) | Select(expr, _) => match expr.as_mut() { |
| 108 | Get { .. } => (), |
| 109 | expr => { |
| 110 | // generate fresh local id |
| 111 | let local_id = id_gen.next().unwrap(); |
| 112 | // generate a `Get(local_id)` to be used as a subquery replacement |
| 113 | let mut subquery = Get { |
| 114 | id: Id::Local(local_id.clone()), |
| 115 | typ: SqlRelationType::empty(), // TODO (aalexandrov) |
| 116 | }; |
| 117 | // swap the current subquery with the replacement |
| 118 | std::mem::swap(expr, &mut subquery); |
| 119 | // push a new $local_id = $subquery binding for a wrapping Let { ... } |
| 120 | bindings.push(Binding { local_id, subquery }); |
| 121 | } |
| 122 | }, |
| 123 | _ => (), |
| 124 | }); |
| 125 | }); |
| 126 | }; |
| 127 | |
| 128 | // Drain the `bindings` stack by wrapping the given `HirRelationExpr` with |
| 129 | // a sequence of `Let { ... }` nodes, one for each binding. |
| 130 | let insert_let_bindings = |expr: &mut HirRelationExpr, bindings: &mut Vec<Binding>| { |
| 131 | for binding in bindings.drain(..) { |
| 132 | let name = format!("subquery-{}", Into::<u64>::into(&binding.local_id)); |
| 133 | let id = binding.local_id; |
| 134 | let value = Box::new(binding.subquery); |
| 135 | let body = Box::new(expr.take()); |
| 136 | *expr = HirRelationExpr::Let { |
| 137 | name, |
| 138 | id, |
| 139 | value, |
| 140 | body, |
no test coverage detected