Skip to content

Commit a02a6a1

Browse files
thomas11jpountz
authored andcommitted
Fix IndexOutOfBoundsException in histograms for NaN doubles (#26787) (#26856)
1 parent 8eafb70 commit a02a6a1

File tree

2 files changed

+28
-3
lines changed

2 files changed

+28
-3
lines changed

core/src/main/java/org/elasticsearch/search/aggregations/bucket/histogram/InternalHistogram.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -321,8 +321,9 @@ protected boolean lessThan(IteratorAndCurrent a, IteratorAndCurrent b) {
321321
do {
322322
final IteratorAndCurrent top = pq.top();
323323

324-
if (top.current.key != key) {
325-
// the key changes, reduce what we already buffered and reset the buffer for current buckets
324+
if (Double.compare(top.current.key, key) != 0) {
325+
// The key changes, reduce what we already buffered and reset the buffer for current buckets.
326+
// Using Double.compare instead of != to handle NaN correctly.
326327
final Bucket reduced = currentBuckets.get(0).reduce(currentBuckets, reduceContext);
327328
if (reduced.getDocCount() >= minDocCount || reduceContext.isFinalReduce() == false) {
328329
reducedBuckets.add(reduced);
@@ -335,7 +336,7 @@ protected boolean lessThan(IteratorAndCurrent a, IteratorAndCurrent b) {
335336

336337
if (top.iterator.hasNext()) {
337338
final Bucket next = top.iterator.next();
338-
assert next.key > top.current.key : "shards must return data sorted by key";
339+
assert Double.compare(next.key, top.current.key) > 0 : "shards must return data sorted by key";
339340
top.current = next;
340341
pq.updateTop();
341342
} else {

core/src/test/java/org/elasticsearch/search/aggregations/bucket/histogram/InternalHistogramTests.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,15 @@
2323
import org.elasticsearch.common.io.stream.Writeable.Reader;
2424
import org.elasticsearch.search.DocValueFormat;
2525
import org.elasticsearch.search.aggregations.BucketOrder;
26+
import org.elasticsearch.search.aggregations.InternalAggregation;
27+
import org.elasticsearch.search.aggregations.InternalAggregation.ReduceContext;
2628
import org.elasticsearch.search.aggregations.InternalAggregations;
2729
import org.elasticsearch.search.aggregations.InternalMultiBucketAggregationTestCase;
2830
import org.elasticsearch.search.aggregations.ParsedMultiBucketAggregation;
2931
import org.elasticsearch.search.aggregations.pipeline.PipelineAggregator;
3032

3133
import java.util.ArrayList;
34+
import java.util.Arrays;
3235
import java.util.HashMap;
3336
import java.util.List;
3437
import java.util.Map;
@@ -63,6 +66,27 @@ protected InternalHistogram createTestInstance(String name,
6366
return new InternalHistogram(name, buckets, order, 1, null, format, keyed, pipelineAggregators, metaData);
6467
}
6568

69+
// issue 26787
70+
public void testHandlesNaN() {
71+
InternalHistogram histogram = createTestInstance();
72+
InternalHistogram histogram2 = createTestInstance();
73+
List<InternalHistogram.Bucket> buckets = histogram.getBuckets();
74+
if (buckets == null || buckets.isEmpty()) {
75+
return;
76+
}
77+
78+
// Set the key of one bucket to NaN. Must be the last bucket because NaN is greater than everything else.
79+
List<InternalHistogram.Bucket> newBuckets = new ArrayList<>(buckets.size());
80+
if (buckets.size() > 1) {
81+
newBuckets.addAll(buckets.subList(0, buckets.size() - 1));
82+
}
83+
InternalHistogram.Bucket b = buckets.get(buckets.size() - 1);
84+
newBuckets.add(new InternalHistogram.Bucket(Double.NaN, b.docCount, keyed, b.format, b.aggregations));
85+
86+
InternalHistogram newHistogram = histogram.create(newBuckets);
87+
newHistogram.doReduce(Arrays.asList(newHistogram, histogram2), new InternalAggregation.ReduceContext(null, null, false));
88+
}
89+
6690
@Override
6791
protected void assertReduced(InternalHistogram reduced, List<InternalHistogram> inputs) {
6892
Map<Double, Long> expectedCounts = new TreeMap<>();

0 commit comments

Comments
 (0)