Skip to content

Commit b5e385f

Browse files
authored
Fix auto_date_histogram interval (#56252) (#56341)
`auto_date_histogram` was returning the incorrect `interval` because of a combination of two things: 1. When pipeline aggregations rewrote `auto_date_histogram` we reset the interval to 1. Oops. Fixed that. 2. *Every* bucket aggregation was rewriting its buckets as though there was a pipeline aggregation even if there aren't any. This is a bit silly so we skip that too. Closes #56116
1 parent bd0e0f4 commit b5e385f

File tree

8 files changed

+125
-11
lines changed

8 files changed

+125
-11
lines changed

rest-api-spec/src/main/resources/rest-api-spec/test/search.aggregation/330_auto_date_histogram.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ setup:
2525

2626
---
2727
"basic":
28+
- skip:
29+
version: " - 7.8.99"
30+
reason: interval had a in bug before 7.9.0
2831
- do:
2932
search:
3033
rest_total_hits_as_int: true
@@ -37,6 +40,7 @@ setup:
3740
buckets: 2
3841
- match: { hits.total: 4 }
3942
- length: { aggregations.histo.buckets: 2 }
43+
- match: { aggregations.histo.interval: "7d" }
4044
- match: { aggregations.histo.buckets.0.key_as_string: "2020-03-01T00:00:00.000Z" }
4145
- match: { aggregations.histo.buckets.0.doc_count: 2 }
4246
- match: { aggregations.histo.buckets.1.key_as_string: "2020-03-08T00:00:00.000Z" }

server/src/main/java/org/elasticsearch/search/aggregations/InternalMultiBucketAggregation.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -152,8 +152,12 @@ public static int countInnerBucket(Aggregation agg) {
152152
public final InternalAggregation reducePipelines(
153153
InternalAggregation reducedAggs, ReduceContext reduceContext, PipelineTree pipelineTree) {
154154
assert reduceContext.isFinalReduce();
155-
List<B> materializedBuckets = reducePipelineBuckets(reduceContext, pipelineTree);
156-
return super.reducePipelines(create(materializedBuckets), reduceContext, pipelineTree);
155+
InternalAggregation reduced = this;
156+
if (pipelineTree.hasSubTrees()) {
157+
List<B> materializedBuckets = reducePipelineBuckets(reduceContext, pipelineTree);
158+
reduced = create(materializedBuckets);
159+
}
160+
return super.reducePipelines(reduced, reduceContext, pipelineTree);
157161
}
158162

159163
@Override

server/src/main/java/org/elasticsearch/search/aggregations/bucket/InternalSingleBucketAggregation.java

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -121,13 +121,17 @@ public InternalAggregation reduce(List<InternalAggregation> aggregations, Reduce
121121
public final InternalAggregation reducePipelines(
122122
InternalAggregation reducedAggs, ReduceContext reduceContext, PipelineTree pipelineTree) {
123123
assert reduceContext.isFinalReduce();
124-
List<InternalAggregation> aggs = new ArrayList<>();
125-
for (Aggregation agg : getAggregations().asList()) {
126-
PipelineTree subTree = pipelineTree.subTree(agg.getName());
127-
aggs.add(((InternalAggregation)agg).reducePipelines((InternalAggregation)agg, reduceContext, subTree));
124+
InternalAggregation reduced = this;
125+
if (pipelineTree.hasSubTrees()) {
126+
List<InternalAggregation> aggs = new ArrayList<>();
127+
for (Aggregation agg : getAggregations().asList()) {
128+
PipelineTree subTree = pipelineTree.subTree(agg.getName());
129+
aggs.add(((InternalAggregation)agg).reducePipelines((InternalAggregation)agg, reduceContext, subTree));
130+
}
131+
InternalAggregations reducedSubAggs = new InternalAggregations(aggs);
132+
reduced = create(reducedSubAggs);
128133
}
129-
InternalAggregations reducedSubAggs = new InternalAggregations(aggs);
130-
return super.reducePipelines(create(reducedSubAggs), reduceContext, pipelineTree);
134+
return super.reducePipelines(reduced, reduceContext, pipelineTree);
131135
}
132136

133137
@Override

server/src/main/java/org/elasticsearch/search/aggregations/bucket/histogram/InternalAutoDateHistogram.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,10 @@ public int hashCode() {
196196
private final DocValueFormat format;
197197
private final BucketInfo bucketInfo;
198198
private final int targetBuckets;
199-
private long bucketInnerInterval;
199+
/**
200+
* The interval within the rounding that the buckets are using.
201+
*/
202+
private final long bucketInnerInterval;
200203

201204
InternalAutoDateHistogram(String name, List<Bucket> buckets, int targetBuckets, BucketInfo emptyBucketInfo, DocValueFormat formatter,
202205
Map<String, Object> metadata, long bucketInnerInterval) {
@@ -217,6 +220,7 @@ public InternalAutoDateHistogram(StreamInput in) throws IOException {
217220
format = in.readNamedWriteable(DocValueFormat.class);
218221
buckets = in.readList(stream -> new Bucket(stream, format));
219222
this.targetBuckets = in.readVInt();
223+
bucketInnerInterval = 1; // Calculated on merge.
220224
}
221225

222226
@Override
@@ -258,7 +262,7 @@ public BucketInfo getBucketInfo() {
258262

259263
@Override
260264
public InternalAutoDateHistogram create(List<Bucket> buckets) {
261-
return new InternalAutoDateHistogram(name, buckets, targetBuckets, bucketInfo, format, metadata, 1);
265+
return new InternalAutoDateHistogram(name, buckets, targetBuckets, bucketInfo, format, metadata, bucketInnerInterval);
262266
}
263267

264268
@Override
@@ -593,7 +597,7 @@ public InternalAggregation createAggregation(List<MultiBucketsAggregation.Bucket
593597
buckets2.add((Bucket) b);
594598
}
595599
buckets2 = Collections.unmodifiableList(buckets2);
596-
return new InternalAutoDateHistogram(name, buckets2, targetBuckets, bucketInfo, format, getMetadata(), 1);
600+
return new InternalAutoDateHistogram(name, buckets2, targetBuckets, bucketInfo, format, getMetadata(), bucketInnerInterval);
597601
}
598602

599603
@Override

server/src/main/java/org/elasticsearch/search/aggregations/pipeline/PipelineAggregator.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,13 @@ public PipelineTree subTree(String name) {
9696
return subTrees.getOrDefault(name, EMPTY);
9797
}
9898

99+
/**
100+
* Return {@code true} if this node in the tree has any subtrees.
101+
*/
102+
public boolean hasSubTrees() {
103+
return false == subTrees.isEmpty();
104+
}
105+
99106
@Override
100107
public String toString() {
101108
return "PipelineTree[" + aggregators + "," + subTrees + "]";

server/src/test/java/org/elasticsearch/search/aggregations/bucket/filter/InternalFilterTests.java

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,22 @@
1919

2020
package org.elasticsearch.search.aggregations.bucket.filter;
2121

22+
import org.elasticsearch.search.aggregations.InternalAggregation;
23+
import org.elasticsearch.search.aggregations.InternalAggregation.ReduceContext;
2224
import org.elasticsearch.search.aggregations.InternalAggregations;
2325
import org.elasticsearch.search.aggregations.InternalSingleBucketAggregationTestCase;
2426
import org.elasticsearch.search.aggregations.bucket.ParsedSingleBucketAggregation;
27+
import org.elasticsearch.search.aggregations.pipeline.PipelineAggregator;
28+
import org.elasticsearch.search.aggregations.pipeline.PipelineAggregator.PipelineTree;
2529

2630
import java.util.List;
2731
import java.util.Map;
2832

33+
import static java.util.Collections.emptyList;
34+
import static java.util.Collections.emptyMap;
35+
import static java.util.Collections.singletonList;
36+
import static org.hamcrest.Matchers.sameInstance;
37+
2938
public class InternalFilterTests extends InternalSingleBucketAggregationTestCase<InternalFilter> {
3039
@Override
3140
protected InternalFilter createTestInstance(String name, long docCount, InternalAggregations aggregations,
@@ -42,4 +51,32 @@ protected void extraAssertReduced(InternalFilter reduced, List<InternalFilter> i
4251
protected Class<? extends ParsedSingleBucketAggregation> implementationClass() {
4352
return ParsedFilter.class;
4453
}
54+
55+
public void testReducePipelinesReturnsSameInstanceWithoutPipelines() {
56+
InternalFilter test = createTestInstance();
57+
assertThat(test.reducePipelines(test, emptyReduceContextBuilder().forFinalReduction(), PipelineTree.EMPTY), sameInstance(test));
58+
}
59+
60+
public void testReducePipelinesReducesBucketPipelines() {
61+
/*
62+
* Tests that a pipeline buckets by creating a mock pipeline that
63+
* replaces "inner" with "dummy".
64+
*/
65+
InternalFilter dummy = createTestInstance();
66+
InternalFilter inner = createTestInstance();
67+
68+
InternalAggregations sub = new InternalAggregations(singletonList(inner));
69+
InternalFilter test = createTestInstance("test", randomNonNegativeLong(), sub, emptyMap());
70+
PipelineAggregator mockPipeline = new PipelineAggregator(null, null, null) {
71+
@Override
72+
public InternalAggregation reduce(InternalAggregation aggregation, ReduceContext reduceContext) {
73+
return dummy;
74+
}
75+
};
76+
PipelineTree tree = new PipelineTree(
77+
org.elasticsearch.common.collect.Map.of(inner.getName(), new PipelineTree(emptyMap(), singletonList(mockPipeline))),
78+
emptyList());
79+
InternalFilter reduced = (InternalFilter) test.reducePipelines(test, emptyReduceContextBuilder().forFinalReduction(), tree);
80+
assertThat(reduced.getAggregations().get(dummy.getName()), sameInstance(dummy));
81+
}
4582
}

server/src/test/java/org/elasticsearch/search/aggregations/bucket/filter/InternalFiltersTests.java

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,13 @@
1919

2020
package org.elasticsearch.search.aggregations.bucket.filter;
2121

22+
import org.elasticsearch.search.aggregations.InternalAggregation;
23+
import org.elasticsearch.search.aggregations.InternalAggregation.ReduceContext;
2224
import org.elasticsearch.search.aggregations.InternalAggregations;
2325
import org.elasticsearch.search.aggregations.ParsedMultiBucketAggregation;
2426
import org.elasticsearch.search.aggregations.bucket.filter.InternalFilters.InternalBucket;
27+
import org.elasticsearch.search.aggregations.pipeline.PipelineAggregator;
28+
import org.elasticsearch.search.aggregations.pipeline.PipelineAggregator.PipelineTree;
2529
import org.elasticsearch.test.InternalMultiBucketAggregationTestCase;
2630

2731
import java.util.ArrayList;
@@ -30,6 +34,11 @@
3034
import java.util.Map;
3135
import java.util.TreeMap;
3236

37+
import static java.util.Collections.emptyList;
38+
import static java.util.Collections.emptyMap;
39+
import static java.util.Collections.singletonList;
40+
import static org.hamcrest.Matchers.sameInstance;
41+
3342
public class InternalFiltersTests extends InternalMultiBucketAggregationTestCase<InternalFilters> {
3443

3544
private boolean keyed;
@@ -109,4 +118,34 @@ protected InternalFilters mutateInstance(InternalFilters instance) {
109118
}
110119
return new InternalFilters(name, buckets, keyed, metadata);
111120
}
121+
122+
public void testReducePipelinesReturnsSameInstanceWithoutPipelines() {
123+
InternalFilters test = createTestInstance();
124+
assertThat(test.reducePipelines(test, emptyReduceContextBuilder().forFinalReduction(), PipelineTree.EMPTY), sameInstance(test));
125+
}
126+
127+
public void testReducePipelinesReducesBucketPipelines() {
128+
/*
129+
* Tests that a pipeline buckets by creating a mock pipeline that
130+
* replaces "inner" with "dummy".
131+
*/
132+
InternalFilters dummy = createTestInstance();
133+
InternalFilters inner = createTestInstance();
134+
135+
InternalAggregations sub = new InternalAggregations(singletonList(inner));
136+
InternalFilters test = createTestInstance("test", emptyMap(), sub);
137+
PipelineAggregator mockPipeline = new PipelineAggregator(null, null, null) {
138+
@Override
139+
public InternalAggregation reduce(InternalAggregation aggregation, ReduceContext reduceContext) {
140+
return dummy;
141+
}
142+
};
143+
PipelineTree tree = new PipelineTree(
144+
org.elasticsearch.common.collect.Map.of(inner.getName(), new PipelineTree(emptyMap(), singletonList(mockPipeline))),
145+
emptyList());
146+
InternalFilters reduced = (InternalFilters) test.reducePipelines(test, emptyReduceContextBuilder().forFinalReduction(), tree);
147+
for (InternalFilters.InternalBucket bucket : reduced.getBuckets()) {
148+
assertThat(bucket.getAggregations().get(dummy.getName()), sameInstance(dummy));
149+
}
150+
}
112151
}

server/src/test/java/org/elasticsearch/search/aggregations/bucket/histogram/InternalAutoDateHistogramTests.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
import static org.hamcrest.Matchers.empty;
5151
import static org.hamcrest.Matchers.equalTo;
5252
import static org.hamcrest.Matchers.greaterThan;
53+
import static org.hamcrest.Matchers.hasSize;
5354

5455
public class InternalAutoDateHistogramTests extends InternalMultiBucketAggregationTestCase<InternalAutoDateHistogram> {
5556

@@ -376,4 +377,18 @@ private List<String> keys(InternalAutoDateHistogram h) {
376377
private List<Integer> docCounts(InternalAutoDateHistogram h) {
377378
return h.getBuckets().stream().map(b -> (int) b.getDocCount()).collect(toList());
378379
}
380+
381+
public void testCreateWithReplacementBuckets() {
382+
InternalAutoDateHistogram noInterval = createTestInstance();
383+
InternalAutoDateHistogram orig = new InternalAutoDateHistogram(
384+
noInterval.getName(), noInterval.getBuckets(), noInterval.getTargetBuckets(), noInterval.getBucketInfo(),
385+
noInterval.getFormatter(), noInterval.getMetadata(), randomLong());
386+
InternalAutoDateHistogram copy = orig.create(emptyList());
387+
assertThat(copy.getName(), equalTo(orig.getName()));
388+
assertThat(copy.getBuckets(), hasSize(0));
389+
assertThat(copy.getTargetBuckets(), equalTo(orig.getTargetBuckets()));
390+
assertThat(copy.getBucketInfo(), equalTo(orig.getBucketInfo()));
391+
assertThat(copy.getFormatter(), equalTo(orig.getFormatter()));
392+
assertThat(copy.getInterval(), equalTo(orig.getInterval()));
393+
}
379394
}

0 commit comments

Comments
 (0)