Skip to content

EnsureRequirements returns Transformed::yes unconditionally, forcing a full plan rebuild #25361

Description

@zhuqi-lucas

Describe the bug

EnsureRequirements returns Transformed::yes unconditionally at three places, so transform_up rebuilds every ancestor node and recomputes its PlanProperties even on a pass that changes nothing, and the rule always hands back a fresh Arc.

// physical-optimizer/src/ensure_requirements/mod.rs
plan.transform_up(|p| Ok(Transformed::yes(reorder_join_keys_to_inputs(p)?)))
    .transform_up(|p| Ok(Transformed::yes(replace_with_partial_sort(p)?)))

// physical-optimizer/src/ensure_requirements/enforce_distribution.rs, end of ensure_distribution
Ok(Transformed::yes(optimized_context))

The flag is what decides whether a node is rebuilt:

// common/src/tree_node.rs
if new_children.transformed {
    self.with_new_arc_children(arc_self, new_children)   // rebuild, recompute properties
} else {
    Ok(Transformed::new(self, false, new_children.tnr))  // reuse the original Arc
}

Reporting yes when nothing changed therefore costs a full rebuild of the plan, and removes the caller's ability to tell that nothing happened.

To Reproduce

Run EnsureRequirements on a plan it has already settled and compare the result with Arc::ptr_eq. It is never equal.

Expected behavior

Report what actually happened. A pointer comparison against the input is enough at each of the three sites:

let before = Arc::clone(&p);
let after = reorder_join_keys_to_inputs(p)?;
Ok(if Arc::ptr_eq(&before, &after) { Transformed::no(after) } else { Transformed::yes(after) })

Additional context

Measured on a real 34-node plan through a chain with six enforcement passes. Of 23 calls that left the plan byte-identical, fixing these three sites makes 7 also return the input object, which callers can then detect for free.

The remaining 16 still rebuild, because the distribution and sorting phases inside the rule change the plan and then change it back. That is a separate problem, filed as #25360.

Found while measuring #25355 / #25356, where this is why plans have to be compared by rendered form rather than by pointer.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions