Fix ordering for UNION ALL over heterogeneous constants#23528
Conversation
`add_sort_above` and `add_sort_above_with_distribution` pruned every expression that equivalence properties reported as constant, via `is_expr_constant(..).is_none()`. That treats `AcrossPartitions::Heterogeneous` the same as `AcrossPartitions::Uniform`, which is wrong. A `Heterogeneous` constant is constant *within* each partition but varies *across* partitions (e.g. `sample` in `SELECT 1 AS sample, c FROM t UNION ALL SELECT 2 AS sample, c FROM t`). Both helpers build a partition-preserving `SortExec` whose output is merged across partitions downstream, where such a column is still the leading ordering discriminator. Dropping it silently discards the global ordering, producing non-deterministic output order for `ORDER BY sample, c` once the plan is rooted at a sink / output requirement. Only `AcrossPartitions::Uniform` is globally constant and safe to drop. Introduce `is_uniform_constant` and use it at both prune sites. Adds a regression test that builds a union with a heterogeneous-constant column and asserts the key survives `add_sort_above`.
|
@alamb Hi! May I ask you to have a look at this tiny bugfix? |
| plan.is::<GlobalLimitExec>() || plan.is::<LocalLimitExec>() | ||
| } | ||
|
|
||
| #[cfg(test)] |
There was a problem hiding this comment.
Can you please write these tests as .slt rather than rust unit tests? That will make it easier to validate that it is hooked up into the right place
There was a problem hiding this comment.
@alamb Changed test to be .slt, thank you for the suggestion, turned out that fix belonged to a different place.
The previous fix touched `add_sort_above`/`add_sort_above_with_distribution` in physical-optimizer, but that path is not where real SQL loses the key. A global sort feeding a sink (`CopyTo`) still dropped its leading ordering key when that key is an `AcrossPartitions::Heterogeneous` constant, producing non-deterministically ordered sink output. The actual prune happens in `EquivalenceGroup::normalize_sort_exprs` and `normalize_sort_requirements`, which filtered out *every* constant via `is_expr_constant(..).is_none()`. That drops `Heterogeneous` constants too, even though such a column is constant only *within* a partition and still discriminates the global order once partitions are merged (e.g. a partition-preserving `SortExec` feeding a `SortPreservingMergeExec` under a sink). Only `AcrossPartitions::Uniform` is globally constant and safe to drop. Revert the physical-optimizer change and instead keep heterogeneous constants at both normalize sites via a new `is_uniform_constant` helper. Replace the Rust unit test with an `.slt` regression test that plans `EXPLAIN COPY (<union> ORDER BY a, b) TO parquet` and asserts the sink-rooted plan keeps `[a@0 ASC, b@1 ASC]` in the merge above the union. This exercises the real planning path end to end rather than calling `add_sort_above` directly. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #23528 +/- ##
=======================================
Coverage ? 80.65%
=======================================
Files ? 1086
Lines ? 366102
Branches ? 366102
=======================================
Hits ? 295273
Misses ? 53234
Partials ? 17595 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
alamb
left a comment
There was a problem hiding this comment.
Thank you @vadimpiven
I took the liberty of pushing a few more tests to your branch and merging up from main
|
@alamb Can you please merge the branch if everything looks good? This bug produces sporadic errors in our pipeline. |
Which issue does this PR close?
Rationale for this change
add_sort_aboveandadd_sort_above_with_distributionin physical-optimizer prune every expression thatEquivalenceProperties::is_expr_constantreports as constant.This treats
AcrossPartitions::Heterogeneousthe same asAcrossPartitions::Uniform, which is incorrect.Check an example:
The union reports sample as a
Heterogeneousconstant and so it gets pruned, which breaksORDER BY.What changes are included in this PR?
Helper that activates pruning only for
AcrossPartitions::Uniform, use it at both prune sites (add_sort_aboveandadd_sort_above_with_distribution) soHeterogeneousconstants are retained.Are these changes tested?
Yes, a new unit-test added.
Are there any user-facing changes?
No API changes, just an internal logic bug-fix.