Skip to content

fix: preserve fetch-carrying operators in remove_dist_changing_operators - #30

Open
v0y4g3r wants to merge 2 commits into
greptimedb-53.1.0-function-signature-exec-errorfrom
fix/enforce-distribution-preserve-fetch
Open

fix: preserve fetch-carrying operators in remove_dist_changing_operators#30
v0y4g3r wants to merge 2 commits into
greptimedb-53.1.0-function-signature-exec-errorfrom
fix/enforce-distribution-preserve-fetch

Conversation

@v0y4g3r

@v0y4g3r v0y4g3r commented Aug 28, 2026

Copy link
Copy Markdown

Problem

EnforceDistribution::remove_dist_changing_operators strips top-level RepartitionExec / CoalescePartitionsExec / SortPreservingMergeExec, assuming they are pure distribution-changing operators that can be regenerated later if necessary.

A fetch on these operators is not a distribution concern — it carries global limit semantics (e.g. produced by LimitPushdown folding a GlobalLimitExec into CoalescePartitionsExec: fetch=N). Stripping the operator silently drops the limit.

This is latent in a single optimizer pass, but breaks when the physical optimizer pipeline runs twice on the same plan (e.g. GreptimeDB plans a nested subplan separately, then optimizes the enclosing plan again — the MergeScan local-execution fallback for information_schema tables): the second pass removes CoalescePartitionsExec: fetch=N and never re-adds it, so

SELECT DISTINCT a FROM t LIMIT 1

can return one row per partition instead of one row in total (GreptimeTeam/greptimedb#8958). Reproducible with vanilla DataFusion 53.1.0 by running the physical optimizer pipeline twice.

Fix

Stop the removal loop at any operator carrying a fetch.

Tests

  • keep_fetch_carrying_dist_changing_operators: EnforceDistribution preserves fetch-carrying CoalescePartitionsExec and SortPreservingMergeExec.
  • test_global_limit_survives_second_optimizer_pass: running the full physical optimizer pipeline a second time over the optimized plan of SELECT DISTINCT a FROM t LIMIT 1 keeps the global limit boundary (CoalescePartitionsExec: fetch=1 / GlobalLimitExec).

Verified: cargo test -p datafusion --test core_integration 917/917 passed; clippy and fmt clean.

Related: apache#23800 (orthogonal LimitPushdown fix — that one mistakes a per-partition fetch for a global limit; this one drops a global fetch when removing distribution operators).

EnforceDistribution::remove_dist_changing_operators strips top-level
RepartitionExec / CoalescePartitionsExec / SortPreservingMergeExec,
assuming they can be regenerated later if necessary. A fetch on these
operators is not a distribution concern but carries global limit
semantics (e.g. produced by LimitPushdown folding a GlobalLimitExec into
CoalescePartitionsExec: fetch=N), so stripping the operator silently
drops the limit.

This is latent in a single optimizer pass, but breaks when the physical
optimizer pipeline runs twice on the same plan (e.g. GreptimeDB plans a
nested subplan separately, then optimizes the enclosing plan again):
the second pass removes CoalescePartitionsExec: fetch=N and never
re-adds it, so a query like SELECT DISTINCT a FROM t LIMIT 1 can return
one row per partition instead of one row in total.

Stop the removal loop at any operator carrying a fetch.

Regression tests:
- EnforceDistribution preserves fetch-carrying CoalescePartitionsExec
  and SortPreservingMergeExec.
- Running the full physical optimizer pipeline a second time over the
  optimized plan of SELECT DISTINCT a FROM t LIMIT 1 keeps the global
  limit boundary.

Related: apache#23800 (orthogonal LimitPushdown fix)
Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>
Copilot AI lite review requested due to automatic review settings August 28, 2026 15:14

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes a correctness bug in the physical optimizer where EnforceDistribution::remove_dist_changing_operators could strip distribution-changing operators that also carry fetch (global limit semantics), causing the global limit boundary to be lost when the physical optimizer pipeline is run multiple times on an already-optimized plan.

Changes:

  • Stop stripping top-level RepartitionExec / CoalescePartitionsExec / SortPreservingMergeExec once an operator with fetch is encountered.
  • Add regression coverage ensuring fetch-carrying distribution-changing operators are preserved by EnforceDistribution.
  • Add an integration regression test ensuring SELECT DISTINCT a ... LIMIT 1 retains a global limit boundary after a second optimizer pass.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
datafusion/physical-optimizer/src/enforce_distribution.rs Preserve fetch-carrying distribution-changing operators during the “strip top operators” phase to avoid dropping global limit semantics.
datafusion/core/tests/physical_optimizer/limited_distinct_aggregation.rs Add regression test that rerunning physical optimization keeps the global limit boundary for a limited DISTINCT query.
datafusion/core/tests/physical_optimizer/enforce_distribution.rs Add targeted unit test that EnforceDistribution keeps fetch-carrying CoalescePartitionsExec and SortPreservingMergeExec.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread datafusion/core/tests/physical_optimizer/limited_distinct_aggregation.rs Outdated
The regression test test_global_limit_survives_second_optimizer_pass
constructed a fresh ConfigOptions::new() and PhysicalOptimizer::new()
for the second optimization pass, which could drift from the
SessionContext's actual optimizer list and configuration.

Reuse ctx.state().physical_optimizers() and ctx.state().config_options()
instead, matching how DefaultPhysicalPlanner::optimize_physical_plan
runs the pipeline in production.

Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants