[BUG] make first/last follow a preceding sort - #5719
Conversation
PR Reviewer Guide 🔍(Review updated until commit a22f944)Here are some key observations to aid the review process:
|
PR Code Suggestions ✨Latest suggestions up to a22f944 Explore these optional code suggestions:
Previous suggestionsSuggestions up to commit fe7d053
Suggestions up to commit fe7d053
Suggestions up to commit 518a04b
Suggestions up to commit a22f944
Suggestions up to commit 6e32d15
|
|
Persistent review updated to latest commit 21cc90f |
|
Persistent review updated to latest commit 6e32d15 |
FIRST and LAST ignored a preceding sort because document order is not globally defined across shards and Calcite can discard input collation below an aggregate. Carry the complete explicit sort tuple into internal FIRST_BY_SORT and LAST_BY_SORT aggregates, including direction, null placement, and IP type. Push down direct field keys as equivalent multi-field top_hits sorts and use a dedicated enumerable implementor that rejects only null measures, allowing nullable sort keys to reach the tuple comparator. Validate the internal operand layout, fail closed when a filtered aggregation cannot resolve its filter Project, and cover multi-shard, null-order, IP, pushdown, no-pushdown, and explain-plan behavior. Document that sorted FIRST/LAST follow the explicit collation while unsorted calls retain natural document order. TAKE remains unchanged. Signed-off-by: Eric Wei <menwe@amazon.com>
6e32d15 to
a22f944
Compare
|
Persistent review updated to latest commit a22f944 |
|
Persistent review updated to latest commit 4436897 |
|
Persistent review updated to latest commit fe7d053 |
1 similar comment
|
Persistent review updated to latest commit fe7d053 |
fe7d053 to
a22f944
Compare
|
Persistent review updated to latest commit a22f944 |
| // FIRST always skips null measures, whether it follows document order or an explicit sort. | ||
| if (candidateValue != null) { | ||
| acc.setValue(candidateValue); | ||
| if (OrderedAggregateUtils.hasSortKeys(values)) { |
There was a problem hiding this comment.
Why not regiester FIRST/LAST as window function. A UDAF itself should not know the input is sorted or not.
@dai-chen has comments on previous PR. #4223 (review)
Description
first(x)andlast(x)ignored a preceding PPLsortbecause document order is not globally defined across shards, and Calcite can discard input collation below an aggregate.When an explicit input sort exists, the planner now resolves
first/lastto internalFIRST_BY_SORT/LAST_BY_SORTaggregates carrying the complete sort tuple. Each key carries its value, direction, null placement, and IP-type metadata. This provides one logical representation for both execution paths:top_hitssorts.LAST_BY_SORTreverses direction and null placement for every key.A pushdown-only dedup sort hint cannot replace the operand encoding: it can tell
AggregateAnalyzerhow to sorttop_hits, but it cannot provide each row's sort-key values to the enumerable accumulator.The implementation preserves existing behavior when no explicit sort exists. Null measures are skipped, null sort keys follow the explicit null placement, IP keys use numeric IP comparison, and equal complete sort tuples remain unordered. Computed/script sort keys fall back to enumerable execution rather than changing null semantics.
Validation
CalciteNoPushdownITF0DAFDABA04C8A0EspotlessCheckandgit diff --checkDeterministic Before/After Evidence
The comparison used the same cluster, the same five-shard index, and the same three documents. Placement was intentionally adversarial: the newest document was forced onto shard 0 and the oldest onto shard 4, so shard-ordinal tie-breaking produces the wrong answer reliably rather than by chance. Only the plugin JAR changed between captures.
The user's intent survives into the logical plan
The following line is identical before and after:
The requested ordering is therefore present in the logical plan. The pre-fix error occurs downstream, where that ordering is not encoded in the generated aggregation request.
Before: pre-fix baseline
Baseline commit:
5f5876dd0(the parent of this PR).top_hitssortFIRSTFIRST($0)2024-01-032024-01-01LASTLAST($0)[{"_doc":{"order":"desc"}}]2024-01-032024-01-03FIRSTemitstop_hitswithout a sort key.LASTsorts on_doc, which is a shard-local Lucene document ID and has no global meaning across shards. Neither request carries the user'screated_atordering.LASThappens to return the expected value because the newest document is on shard 0, so the fallback shard-ordinal tie-break selects it. Changing document placement can change the result.After: this PR
A representative rewritten aggregate is:
The aggregate now carries the sort key, direction (
falsefor ascending), null placement (truefor nulls first), and IP-type flag. It also carries a non-null filter for the measure.top_hitssortFIRSTcreated_at ASC,missing: _first2024-01-012024-01-01LASTcreated_at DESC,missing: _last2024-01-032024-01-03FIRSTandLASTare also wrapped in anexists(created_at)filter aggregation, preserving their first/last-non-null contract during pushdown. The@timestampalias is resolved to its concrete mapped field,created_at, in the generated DSL.What this proves
OpenSearch performs a global sort-aware
top_hitsmerge when the request includes comparable field-sort values. ForFIRSTandLAST, the explicit-sort contract makes dropping the available collation a confirmed product defect; this PR preserves that collation through planning and request generation.The capture above validates pushdown. The same adversarial fixture was also executed with pushdown disabled:
FIRSTLAST2024-01-012024-01-032024-01-012024-01-03FIRSTandLASTagree and return the expected values on both paths.Tie behavior is not part of this comparison
The fixture uses three distinct timestamps. Rows with equal complete sort tuples remain unordered by design and are not involved in this before/after result.
Related Issues
Addresses the FIRST/LAST portions of #5716.
Previous PR: #4223
Check List
--signoffor-s.By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.