Extracts `MoveTowardsLeafNodes` sub-expressions from a plan node. Works for any number of inputs (0, 1, 2, …N). For multi-input nodes like Join, each extracted sub-expression is routed to the correct input by checking which input's schema contains all of the expression's column references.
(
plan: LogicalPlan,
alias_generator: &Arc<AliasGenerator>,
)
| 169 | /// by checking which input's schema contains all of the expression's column |
| 170 | /// references. |
| 171 | fn extract_from_plan( |
| 172 | plan: LogicalPlan, |
| 173 | alias_generator: &Arc<AliasGenerator>, |
| 174 | ) -> Result<Transformed<LogicalPlan>> { |
| 175 | // Only extract from plan types whose output schema is predictable after |
| 176 | // expression rewriting. Nodes like Window derive column names from |
| 177 | // their expressions, so rewriting `get_field` inside a window function |
| 178 | // changes the output schema and breaks the recovery projection. |
| 179 | if !matches!( |
| 180 | &plan, |
| 181 | LogicalPlan::Aggregate(_) |
| 182 | | LogicalPlan::Filter(_) |
| 183 | | LogicalPlan::Sort(_) |
| 184 | | LogicalPlan::Limit(_) |
| 185 | | LogicalPlan::Join(_) |
| 186 | ) { |
| 187 | return Ok(Transformed::no(plan)); |
| 188 | } |
| 189 | |
| 190 | let inputs = plan.inputs(); |
| 191 | if inputs.is_empty() { |
| 192 | return Ok(Transformed::no(plan)); |
| 193 | } |
| 194 | |
| 195 | // Fast pre-check: skip all allocations if no extractable expressions exist |
| 196 | if !has_extractable_expr(&plan.expressions()) { |
| 197 | return Ok(Transformed::no(plan)); |
| 198 | } |
| 199 | |
| 200 | // Save original output schema before any transformation |
| 201 | let original_schema = Arc::clone(plan.schema()); |
| 202 | |
| 203 | // Build per-input schemas from borrowed inputs (before plan is consumed |
| 204 | // by map_expressions). We only need schemas and column sets for routing; |
| 205 | // the actual inputs are cloned later only if extraction succeeds. |
| 206 | let input_schemas: Vec<Arc<DFSchema>> = |
| 207 | inputs.iter().map(|i| Arc::clone(i.schema())).collect(); |
| 208 | |
| 209 | // Build per-input extractors |
| 210 | let mut extractors: Vec<LeafExpressionExtractor> = input_schemas |
| 211 | .iter() |
| 212 | .map(|schema| LeafExpressionExtractor::new(schema.as_ref(), alias_generator)) |
| 213 | .collect(); |
| 214 | |
| 215 | // Build per-input column sets for routing expressions to the correct input |
| 216 | let input_column_sets: Vec<std::collections::HashSet<ColumnReference>> = |
| 217 | input_schemas |
| 218 | .iter() |
| 219 | .map(|schema| schema_columns(schema.as_ref())) |
| 220 | .collect(); |
| 221 | |
| 222 | // Transform expressions via map_expressions with routing |
| 223 | let transformed = plan.map_expressions(|expr| { |
| 224 | routing_extract(expr, &mut extractors, &input_column_sets) |
| 225 | })?; |
| 226 | |
| 227 | // If no expressions were rewritten, nothing was extracted |
| 228 | if !transformed.transformed { |
no test coverage detected
searching dependent graphs…