cning112 opened a new pull request, #26455:
URL: https://github.com/apache/datafusion/pull/26455

   ## Which issue does this PR close?
   
   - Closes #26454.
   
   ## Rationale for this change
   
   `ORDER BY ... LIMIT` over a `UNION ALL` returns wrong rows when a leading 
sort key is a constant with a different value in each arm:
   
   ```sql
   SELECT 5 AS c0, id AS c1 FROM t
   UNION ALL
   SELECT 1 AS c0, id AS c1 FROM t WHERE g = 0
   ORDER BY c0, c1 LIMIT 2
   -- returns (1, 0), (5, 0); expected (1, 0), (1, 1)
   ```
   
   The optimizer plans a partitioned TopK under a `SortPreservingMergeExec` and 
drops `c0` from the TopK key, because `c0` is constant within each partition. 
All partitions of one `SortExec` share a single TopK dynamic filter threshold. 
The `c0 = 5` partition publishes a threshold on `c1` alone, and the `c0 = 1` 
partition then discards a row it needs. The issue has the full walk-through.
   
   ## What changes are included in this PR?
   
   `add_sort_above` and `add_sort_above_with_distribution` in 
`datafusion/physical-optimizer/src/utils.rs` both remove sort keys that are 
constant in the input. They now share one helper, `sort_ordering_above`, which 
still removes them, with one exception. When the new `SortExec` has a `fetch` 
and its input has more than one partition, a constant that differs between 
partitions (`AcrossPartitions::Heterogeneous`) stays in the key. The shared 
threshold then compares the full key, which is comparable across partitions.
   
   Nothing else changes:
   
   - `Uniform` constants are still removed.
   - Sorts without a `fetch` still remove `Heterogeneous` constants. A full 
per-partition sort has no shared threshold, and the merge above it sorts on the 
full key.
   - With a single input partition the sort is not partitioned, so nothing is 
shared.
   
   With the change, the plan for the query above is:
   
   ```
   SortPreservingMergeExec: [c0@0 ASC NULLS LAST, c1@1 ASC NULLS LAST], fetch=2
     SortExec: TopK(fetch=2), expr=[c0@0 ASC NULLS LAST, c1@1 ASC NULLS LAST], 
preserve_partitioning=[true], sort_prefix=[c0@0 ASC NULLS LAST]
   ```
   
   The longer key gives the TopK a `sort_prefix` of `[c0]`, because each 
partition's input is trivially ordered by its constant `c0`.
   
   Why the fix lives here: `EXPLAIN VERBOSE` shows the shortened `SortExec: 
TopK(fetch=2), expr=[c1@1 ...]` already in the plan right after 
`EnsureRequirements`, with the fetch attached. No later rule changes it. A 
temporary print in the two helpers showed `parallelize_sorts` building it 
through `add_sort_above_with_check` → `add_sort_above`, and `pushdown_sorts` 
building it again through `add_sort_above_with_distribution`. Both calls had 
`fetch = Some(2)`, two input partitions, and `c0` reported as `Heterogeneous`. 
So both helpers need the change, and fixing only one would leave the bug.
   
   #23528 fixed the same `Uniform`/`Heterogeneous` confusion in 
`EquivalenceGroup`'s sort normalization. This PR deals only with the fetch case 
in these two helpers.
   
   Open PRs #25408 and #25065 change the callers of these helpers 
(`enforce_sorting/sort_pushdown.rs` and `enforce_sorting/mod.rs`) but not 
`utils.rs`. This PR should not conflict with them textually, but whichever 
merges second should re-run `topk.slt`.
   
   ## What is the testing strategy for this PR?
   
   New cases at the end of `datafusion/sqllogictest/test_files/topk.slt`, on a 
`VALUES` table:
   
   - the query above with an `EXPLAIN` showing the TopK key, at 
`target_partitions = 1` and `4`;
   - the same query without the filter;
   - `LIMIT 3` and `ORDER BY c0 DESC, c1` as controls.
   
   On `main` without the fix, four of them fail: the `EXPLAIN`, `LIMIT 2` at 
both partition counts, and the no-filter query. The `LIMIT 3` and `DESC` 
controls pass both before and after.
   
   No existing `.slt` plan changes. `--complete` over the whole suite rewrote 
nothing but trailing newlines in three unrelated files, and I reverted those.
   
   ## Are there any user-facing changes?
   
   The wrong results are fixed. Plans that hit this case now show the constant 
in the TopK `expr`. There are no API changes.
   
   ## Unknowns / assumptions
   
   - **Shared prefix threshold.** The new key gives the TopK a `sort_prefix` of 
`[c0]`. That enables the shared-prefix early exit from #22991 for these plans. 
My reasoning is that the exit stays correct because it compares on the full key 
prefix, which now includes `c0`. The `DESC` case exercises the opposite 
partition order, but I have not written a test that forces either partition to 
finish first.
   - **Merge above the sort.** `add_sort_above_with_distribution` wraps the 
sort in a `SortPreservingMergeExec` with the same shortened ordering when the 
parent requires a single partition. Without a fetch, that merge could also lose 
a `Heterogeneous` key. I could not build a query that reaches it, and this PR 
does not change it.
   - **Other places that attach a fetch.** I assume no other rule attaches a 
fetch to a partitioned `SortExec` whose key was already shortened. In the 
queries I tried, the fetch is already set when `EnsureRequirements` builds the 
sort: plain `LIMIT`, `LIMIT ... OFFSET`, and a filter over the union. 
`LimitPushdown` left those plans unchanged. I have not audited every caller of 
`SortExec::with_fetch`.
   - **The no-filter variant.** Its result on `main` depends on how the 
partitions run: wrong in the sqllogictest, right over a single-batch `MemTable` 
in my runs. I did not dig into why.
   - **Alternative fix.** The other option is to stop sharing the TopK 
threshold across partitions when the input has a `Heterogeneous` constant. That 
would be a larger change inside TopK and would give up the shared filter. I 
went with the smaller planner change, but I am happy to switch if maintainers 
prefer it.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to