Skip to content

Commit 646d2af

Browse files
adriangbclaude
andcommitted
refactor: keep map_children_mut private to the optimizer crate
`map_children_mut` was added as a public method on `LogicalPlan` in `datafusion-expr` only because the optimizer, in a different crate, needed to call it. But the optimizer is its sole consumer, and the `Arc::make_mut` in-place trick does not generalize to the other tree types (`Expr` children are `Box`ed; `PhysicalExpr`/`ExecutionPlan` children are `Arc<dyn _>`, which `Arc::make_mut` cannot handle), so committing to it as public API is not warranted. Move it into `optimizer.rs` as a private free function next to `rewrite_plan_in_place`, its only caller. `datafusion-expr` is now untouched by this PR except for the `map_subqueries` short-circuit, and the optimizer adds no public API. If `TreeNode` ever grows an in-place traversal this logic can move there with no breaking change. No behavior change; the 713 datafusion-optimizer tests pass unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 3e1f856 commit 646d2af

2 files changed

Lines changed: 123 additions & 118 deletions

File tree

datafusion/expr/src/logical_plan/tree_node.rs

Lines changed: 0 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@ use datafusion_common::tree_node::{
5252
TreeNodeRewriter, TreeNodeVisitor,
5353
};
5454
use datafusion_common::{Result, internal_err};
55-
use std::sync::Arc;
5655

5756
impl TreeNode for LogicalPlan {
5857
fn apply_children<'n, F: FnMut(&'n Self) -> Result<TreeNodeRecursion>>(
@@ -394,121 +393,6 @@ macro_rules! handle_transform_recursion {
394393
}
395394

396395
impl LogicalPlan {
397-
/// Applies `f` to each child (input) of this plan node in place,
398-
/// using [`Arc::make_mut`] for copy-on-write semantics.
399-
///
400-
/// When the `Arc` refcount is 1 (the common case in the optimizer),
401-
/// `Arc::make_mut` returns a `&mut` reference without cloning.
402-
/// When the refcount is >1, it clones the inner value first.
403-
///
404-
/// Returns `Ok(true)` if any child was modified by `f`.
405-
///
406-
/// # Error semantics
407-
///
408-
/// If `f` returns `Err` for a child, this method returns that error
409-
/// immediately. Children visited earlier in the same call keep whatever
410-
/// modifications `f` already applied to them — they are **not** rolled
411-
/// back. Callers that need the pre-call tree on error must save a copy
412-
/// beforehand.
413-
pub fn map_children_mut<F: FnMut(&mut Self) -> Result<bool>>(
414-
&mut self,
415-
mut f: F,
416-
) -> Result<bool> {
417-
Ok(match self {
418-
LogicalPlan::Projection(Projection { input, .. })
419-
| LogicalPlan::Filter(Filter { input, .. })
420-
| LogicalPlan::Repartition(Repartition { input, .. })
421-
| LogicalPlan::Window(Window { input, .. })
422-
| LogicalPlan::Aggregate(Aggregate { input, .. })
423-
| LogicalPlan::Sort(Sort { input, .. })
424-
| LogicalPlan::Limit(Limit { input, .. })
425-
| LogicalPlan::SubqueryAlias(SubqueryAlias { input, .. })
426-
| LogicalPlan::Analyze(Analyze { input, .. })
427-
| LogicalPlan::Dml(DmlStatement { input, .. })
428-
| LogicalPlan::Copy(CopyTo { input, .. })
429-
| LogicalPlan::Unnest(Unnest { input, .. }) => f(Arc::make_mut(input))?,
430-
LogicalPlan::Subquery(Subquery { subquery, .. }) => {
431-
f(Arc::make_mut(subquery))?
432-
}
433-
LogicalPlan::Join(Join { left, right, .. }) => {
434-
let l = f(Arc::make_mut(left))?;
435-
let r = f(Arc::make_mut(right))?;
436-
l || r
437-
}
438-
LogicalPlan::Union(Union { inputs, .. }) => {
439-
let mut changed = false;
440-
for input in inputs {
441-
changed |= f(Arc::make_mut(input))?;
442-
}
443-
changed
444-
}
445-
LogicalPlan::Distinct(Distinct::All(input)) => f(Arc::make_mut(input))?,
446-
LogicalPlan::Distinct(Distinct::On(DistinctOn { input, .. })) => {
447-
f(Arc::make_mut(input))?
448-
}
449-
LogicalPlan::Explain(Explain { plan, .. }) => f(Arc::make_mut(plan))?,
450-
LogicalPlan::Ddl(DdlStatement::CreateMemoryTable(CreateMemoryTable {
451-
input,
452-
..
453-
}))
454-
| LogicalPlan::Ddl(DdlStatement::CreateView(CreateView { input, .. })) => {
455-
f(Arc::make_mut(input))?
456-
}
457-
LogicalPlan::RecursiveQuery(RecursiveQuery {
458-
static_term,
459-
recursive_term,
460-
..
461-
}) => {
462-
let s = f(Arc::make_mut(static_term))?;
463-
let r = f(Arc::make_mut(recursive_term))?;
464-
s || r
465-
}
466-
LogicalPlan::Statement(Statement::Prepare(p)) => {
467-
f(Arc::make_mut(&mut p.input))?
468-
}
469-
LogicalPlan::Extension(Extension { node }) => {
470-
let inputs = node.inputs();
471-
if inputs.is_empty() {
472-
false
473-
} else {
474-
// Extension nodes don't expose mutable children,
475-
// fall back to the ownership-based API
476-
let mut changed = false;
477-
let exprs = node.expressions();
478-
let new_inputs: Vec<LogicalPlan> = inputs
479-
.into_iter()
480-
.map(|input| {
481-
let mut plan = input.clone();
482-
if f(&mut plan)? {
483-
changed = true;
484-
}
485-
Ok(plan)
486-
})
487-
.collect::<Result<Vec<_>>>()?;
488-
if changed {
489-
*node = node.with_exprs_and_inputs(exprs, new_inputs)?;
490-
}
491-
changed
492-
}
493-
}
494-
// plans without inputs
495-
LogicalPlan::TableScan { .. }
496-
| LogicalPlan::EmptyRelation { .. }
497-
| LogicalPlan::Values { .. }
498-
| LogicalPlan::DescribeTable(_)
499-
| LogicalPlan::Ddl(DdlStatement::CreateExternalTable(_))
500-
| LogicalPlan::Ddl(DdlStatement::CreateCatalogSchema(_))
501-
| LogicalPlan::Ddl(DdlStatement::CreateCatalog(_))
502-
| LogicalPlan::Ddl(DdlStatement::CreateIndex(_))
503-
| LogicalPlan::Ddl(DdlStatement::DropTable(_))
504-
| LogicalPlan::Ddl(DdlStatement::DropView(_))
505-
| LogicalPlan::Ddl(DdlStatement::DropCatalogSchema(_))
506-
| LogicalPlan::Ddl(DdlStatement::CreateFunction(_))
507-
| LogicalPlan::Ddl(DdlStatement::DropFunction(_))
508-
| LogicalPlan::Statement(_) => false,
509-
})
510-
}
511-
512396
/// Calls `f` on all expressions in the current `LogicalPlan` node.
513397
///
514398
/// # Notes

datafusion/optimizer/src/optimizer.rs

Lines changed: 123 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,14 @@ use datafusion_common::tree_node::{
3232
Transformed, TreeNode, TreeNodeRecursion, TreeNodeRewriter,
3333
};
3434
use datafusion_common::{DFSchema, DataFusionError, HashSet, Result, internal_err};
35-
use datafusion_expr::Expr;
35+
use datafusion_expr::dml::CopyTo;
3636
use datafusion_expr::logical_plan::LogicalPlan;
37+
use datafusion_expr::{
38+
Aggregate, Analyze, CreateMemoryTable, CreateView, DdlStatement, Distinct,
39+
DistinctOn, DmlStatement, Explain, Expr, Extension, Filter, Join, Limit, Projection,
40+
RecursiveQuery, Repartition, Sort, Statement, Subquery, SubqueryAlias, Union, Unnest,
41+
Window,
42+
};
3743

3844
use crate::common_subexpr_eliminate::CommonSubexprEliminate;
3945
use crate::decorrelate_lateral_join::DecorrelateLateralJoin;
@@ -360,6 +366,121 @@ impl TreeNodeRewriter for Rewriter<'_> {
360366
}
361367
}
362368

369+
/// Applies `f` to each child (input) of `plan` in place, using
370+
/// [`Arc::make_mut`] for copy-on-write semantics on `Arc<LogicalPlan>`
371+
/// children. When the `Arc` refcount is 1 (the common case here)
372+
/// `Arc::make_mut` hands out a `&mut` without cloning; when it is >1 the
373+
/// inner value is cloned first.
374+
///
375+
/// Returns `Ok(true)` if any child was modified by `f`.
376+
///
377+
/// This is deliberately private to the optimizer rather than a method on
378+
/// [`LogicalPlan`]: it is an implementation detail of in-place rewriting, and
379+
/// the `Arc::make_mut` approach does not generalize to the other tree types
380+
/// (`Expr` children are `Box`ed; `PhysicalExpr`/`ExecutionPlan` children are
381+
/// `Arc<dyn _>`, which `Arc::make_mut` cannot handle). If `TreeNode` ever
382+
/// grows an in-place traversal this logic can move there.
383+
///
384+
/// # Error semantics
385+
///
386+
/// If `f` returns `Err` for a child, that error is returned immediately;
387+
/// children visited earlier keep whatever modifications `f` already applied
388+
/// to them — they are **not** rolled back.
389+
fn map_children_mut<F: FnMut(&mut LogicalPlan) -> Result<bool>>(
390+
plan: &mut LogicalPlan,
391+
mut f: F,
392+
) -> Result<bool> {
393+
Ok(match plan {
394+
LogicalPlan::Projection(Projection { input, .. })
395+
| LogicalPlan::Filter(Filter { input, .. })
396+
| LogicalPlan::Repartition(Repartition { input, .. })
397+
| LogicalPlan::Window(Window { input, .. })
398+
| LogicalPlan::Aggregate(Aggregate { input, .. })
399+
| LogicalPlan::Sort(Sort { input, .. })
400+
| LogicalPlan::Limit(Limit { input, .. })
401+
| LogicalPlan::SubqueryAlias(SubqueryAlias { input, .. })
402+
| LogicalPlan::Analyze(Analyze { input, .. })
403+
| LogicalPlan::Dml(DmlStatement { input, .. })
404+
| LogicalPlan::Copy(CopyTo { input, .. })
405+
| LogicalPlan::Unnest(Unnest { input, .. }) => f(Arc::make_mut(input))?,
406+
LogicalPlan::Subquery(Subquery { subquery, .. }) => f(Arc::make_mut(subquery))?,
407+
LogicalPlan::Join(Join { left, right, .. }) => {
408+
let l = f(Arc::make_mut(left))?;
409+
let r = f(Arc::make_mut(right))?;
410+
l || r
411+
}
412+
LogicalPlan::Union(Union { inputs, .. }) => {
413+
let mut changed = false;
414+
for input in inputs {
415+
changed |= f(Arc::make_mut(input))?;
416+
}
417+
changed
418+
}
419+
LogicalPlan::Distinct(Distinct::All(input)) => f(Arc::make_mut(input))?,
420+
LogicalPlan::Distinct(Distinct::On(DistinctOn { input, .. })) => {
421+
f(Arc::make_mut(input))?
422+
}
423+
LogicalPlan::Explain(Explain { plan, .. }) => f(Arc::make_mut(plan))?,
424+
LogicalPlan::Ddl(DdlStatement::CreateMemoryTable(CreateMemoryTable {
425+
input,
426+
..
427+
}))
428+
| LogicalPlan::Ddl(DdlStatement::CreateView(CreateView { input, .. })) => {
429+
f(Arc::make_mut(input))?
430+
}
431+
LogicalPlan::RecursiveQuery(RecursiveQuery {
432+
static_term,
433+
recursive_term,
434+
..
435+
}) => {
436+
let s = f(Arc::make_mut(static_term))?;
437+
let r = f(Arc::make_mut(recursive_term))?;
438+
s || r
439+
}
440+
LogicalPlan::Statement(Statement::Prepare(p)) => f(Arc::make_mut(&mut p.input))?,
441+
LogicalPlan::Extension(Extension { node }) => {
442+
let inputs = node.inputs();
443+
if inputs.is_empty() {
444+
false
445+
} else {
446+
// Extension nodes don't expose mutable children,
447+
// fall back to the ownership-based API
448+
let mut changed = false;
449+
let exprs = node.expressions();
450+
let new_inputs: Vec<LogicalPlan> = inputs
451+
.into_iter()
452+
.map(|input| {
453+
let mut plan = input.clone();
454+
if f(&mut plan)? {
455+
changed = true;
456+
}
457+
Ok(plan)
458+
})
459+
.collect::<Result<Vec<_>>>()?;
460+
if changed {
461+
*node = node.with_exprs_and_inputs(exprs, new_inputs)?;
462+
}
463+
changed
464+
}
465+
}
466+
// plans without inputs
467+
LogicalPlan::TableScan { .. }
468+
| LogicalPlan::EmptyRelation { .. }
469+
| LogicalPlan::Values { .. }
470+
| LogicalPlan::DescribeTable(_)
471+
| LogicalPlan::Ddl(DdlStatement::CreateExternalTable(_))
472+
| LogicalPlan::Ddl(DdlStatement::CreateCatalogSchema(_))
473+
| LogicalPlan::Ddl(DdlStatement::CreateCatalog(_))
474+
| LogicalPlan::Ddl(DdlStatement::CreateIndex(_))
475+
| LogicalPlan::Ddl(DdlStatement::DropTable(_))
476+
| LogicalPlan::Ddl(DdlStatement::DropView(_))
477+
| LogicalPlan::Ddl(DdlStatement::DropCatalogSchema(_))
478+
| LogicalPlan::Ddl(DdlStatement::CreateFunction(_))
479+
| LogicalPlan::Ddl(DdlStatement::DropFunction(_))
480+
| LogicalPlan::Statement(_) => false,
481+
})
482+
}
483+
363484
/// Rewrites a plan tree in place using `Arc::make_mut` for
364485
/// copy-on-write semantics on `Arc<LogicalPlan>` children.
365486
///
@@ -406,7 +527,7 @@ fn rewrite_plan_in_place(
406527
}
407528

408529
// Recurse into children using Arc::make_mut (zero-cost when refcount == 1)
409-
changed |= plan.map_children_mut(|child| {
530+
changed |= map_children_mut(plan, |child| {
410531
rewrite_plan_in_place(child, apply_order, rule, config)
411532
})?;
412533

0 commit comments

Comments
 (0)