Is your feature request related to a problem or challenge?
When an Extension node (e.g. view matching OneOf) has many children but no expressions, LogicalPlan::map_expressions still clones all inputs and calls with_exprs_and_inputs to reconstruct the node — wasted work since there are no expressions to transform.
In our production system with view matching (5 MV candidates × ~15 optimizer rules), this unnecessary rebuild is the dominant cost of the optimizer pass for plans containing Extension nodes.
Describe the solution you'd like
Add an early return in map_expressions when node.expressions() is empty:
LogicalPlan::Extension(Extension { node }) => {
let raw_exprs = node.expressions();
if raw_exprs.is_empty() {
Transformed::no(LogicalPlan::Extension(Extension { node }))
} else {
// existing clone + rebuild path
}
}
Describe alternatives you've considered
A more general optimization would be to change UserDefinedLogicalNode::expressions() to return references (&[Expr]) instead of cloned Vec<Expr>, and only clone + rebuild when the transform actually modifies an expression. This would avoid the clone + with_exprs_and_inputs rebuild even for non-empty expression lists when the transform is a no-op. This would be a larger API change affecting all UserDefinedLogicalNode implementations, so the empty-expressions shortcut is a pragmatic first step.
Additional context
Benchmark results (criterion micro-benchmark):
| Children |
no_expr (optimized) |
with_expr (rebuild) |
Speedup |
| 1 |
24 ns |
167 ns |
7x |
| 3 |
23 ns |
192 ns |
8x |
| 5 |
23 ns |
181 ns |
8x |
| 10 |
24 ns |
216 ns |
9x |
The no_expr path is constant time regardless of children count.
Is your feature request related to a problem or challenge?
When an Extension node (e.g. view matching OneOf) has many children but no expressions,
LogicalPlan::map_expressionsstill clones all inputs and callswith_exprs_and_inputsto reconstruct the node — wasted work since there are no expressions to transform.In our production system with view matching (5 MV candidates × ~15 optimizer rules), this unnecessary rebuild is the dominant cost of the optimizer pass for plans containing Extension nodes.
Describe the solution you'd like
Add an early return in
map_expressionswhennode.expressions()is empty:Describe alternatives you've considered
A more general optimization would be to change
UserDefinedLogicalNode::expressions()to return references (&[Expr]) instead of clonedVec<Expr>, and only clone + rebuild when the transform actually modifies an expression. This would avoid the clone +with_exprs_and_inputsrebuild even for non-empty expression lists when the transform is a no-op. This would be a larger API change affecting allUserDefinedLogicalNodeimplementations, so the empty-expressions shortcut is a pragmatic first step.Additional context
Benchmark results (criterion micro-benchmark):
The
no_exprpath is constant time regardless of children count.