From 3c66ac06aee2401db8f6554a7a6a0a6282b7f187 Mon Sep 17 00:00:00 2001 From: Tanguy Leroux Date: Wed, 10 May 2017 10:17:25 +0200 Subject: [PATCH] Add parsing for String/Long/Double Terms aggregations (#24521) --- .../search/aggregations/Aggregations.java | 2 +- .../ParsedMultiBucketAggregation.java | 42 +++-- .../bucket/histogram/ParsedDateHistogram.java | 29 ++-- .../bucket/histogram/ParsedHistogram.java | 23 ++- .../bucket/terms/ParsedDoubleTerms.java | 85 ++++++++++ .../bucket/terms/ParsedLongTerms.java | 85 ++++++++++ .../bucket/terms/ParsedStringTerms.java | 85 ++++++++++ .../bucket/terms/ParsedTerms.java | 151 ++++++++++++++++++ .../percentiles/ParsedPercentiles.java | 3 +- .../InternalAggregationTestCase.java | 19 ++- .../bucket/terms/DoubleTermsTests.java | 23 +-- .../bucket/terms/InternalTermsTestCase.java | 32 +++- .../bucket/terms/LongTermsTests.java | 22 +-- .../bucket/terms/StringTermsTests.java | 20 ++- 14 files changed, 542 insertions(+), 79 deletions(-) create mode 100644 core/src/main/java/org/elasticsearch/search/aggregations/bucket/terms/ParsedDoubleTerms.java create mode 100644 core/src/main/java/org/elasticsearch/search/aggregations/bucket/terms/ParsedLongTerms.java create mode 100644 core/src/main/java/org/elasticsearch/search/aggregations/bucket/terms/ParsedStringTerms.java create mode 100644 core/src/main/java/org/elasticsearch/search/aggregations/bucket/terms/ParsedTerms.java diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/Aggregations.java b/core/src/main/java/org/elasticsearch/search/aggregations/Aggregations.java index 465cef30877..05521e48831 100644 --- a/core/src/main/java/org/elasticsearch/search/aggregations/Aggregations.java +++ b/core/src/main/java/org/elasticsearch/search/aggregations/Aggregations.java @@ -47,7 +47,7 @@ public class Aggregations implements Iterable, ToXContent { protected Aggregations() { } - protected Aggregations(List aggregations) { + public Aggregations(List aggregations) { this.aggregations = aggregations; } diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/ParsedMultiBucketAggregation.java b/core/src/main/java/org/elasticsearch/search/aggregations/ParsedMultiBucketAggregation.java index b2823669a75..02e29a1746f 100644 --- a/core/src/main/java/org/elasticsearch/search/aggregations/ParsedMultiBucketAggregation.java +++ b/core/src/main/java/org/elasticsearch/search/aggregations/ParsedMultiBucketAggregation.java @@ -19,6 +19,7 @@ package org.elasticsearch.search.aggregations; +import org.elasticsearch.common.CheckedBiConsumer; import org.elasticsearch.common.CheckedFunction; import org.elasticsearch.common.xcontent.ObjectParser; import org.elasticsearch.common.xcontent.XContentBuilder; @@ -33,10 +34,11 @@ import java.util.function.Supplier; import static org.elasticsearch.common.xcontent.XContentParserUtils.ensureExpectedToken; -public abstract class ParsedMultiBucketAggregation extends ParsedAggregation implements MultiBucketsAggregation { +public abstract class ParsedMultiBucketAggregation + extends ParsedAggregation implements MultiBucketsAggregation { - protected final List> buckets = new ArrayList<>(); - protected boolean keyed; + protected final List buckets = new ArrayList<>(); + protected boolean keyed = false; @Override protected XContentBuilder doXContentBody(XContentBuilder builder, Params params) throws IOException { @@ -45,7 +47,7 @@ public abstract class ParsedMultiBucketAggregation extends ParsedAggregation imp } else { builder.startArray(CommonFields.BUCKETS.getPreferredName()); } - for (ParsedBucket bucket : buckets) { + for (B bucket : buckets) { bucket.toXContent(builder, params); } if (keyed) { @@ -57,8 +59,8 @@ public abstract class ParsedMultiBucketAggregation extends ParsedAggregation imp } protected static void declareMultiBucketAggregationFields(final ObjectParser objectParser, - final CheckedFunction, IOException> bucketParser, - final CheckedFunction, IOException> keyedBucketParser) { + final CheckedFunction bucketParser, + final CheckedFunction keyedBucketParser) { declareAggregationFields(objectParser); objectParser.declareField((parser, aggregation, context) -> { XContentParser.Token token = parser.currentToken(); @@ -76,23 +78,13 @@ public abstract class ParsedMultiBucketAggregation extends ParsedAggregation imp }, CommonFields.BUCKETS, ObjectParser.ValueType.OBJECT_ARRAY); } - public static class ParsedBucket implements MultiBucketsAggregation.Bucket { + public static abstract class ParsedBucket implements MultiBucketsAggregation.Bucket { private Aggregations aggregations; - private T key; private String keyAsString; private long docCount; private boolean keyed; - protected void setKey(T key) { - this.key = key; - } - - @Override - public Object getKey() { - return key; - } - protected void setKeyAsString(String keyAsString) { this.keyAsString = keyAsString; } @@ -137,17 +129,21 @@ public abstract class ParsedMultiBucketAggregation extends ParsedAggregation imp if (keyAsString != null) { builder.field(CommonFields.KEY_AS_STRING.getPreferredName(), getKeyAsString()); } - builder.field(CommonFields.KEY.getPreferredName(), key); + keyToXContent(builder); builder.field(CommonFields.DOC_COUNT.getPreferredName(), docCount); aggregations.toXContentInternal(builder, params); builder.endObject(); return builder; } - protected static > B parseXContent(final XContentParser parser, - final boolean keyed, - final Supplier bucketSupplier, - final CheckedFunction keyParser) + protected XContentBuilder keyToXContent(XContentBuilder builder) throws IOException { + return builder.field(CommonFields.KEY.getPreferredName(), getKey()); + } + + protected static B parseXContent(final XContentParser parser, + final boolean keyed, + final Supplier bucketSupplier, + final CheckedBiConsumer keyConsumer) throws IOException { final B bucket = bucketSupplier.get(); bucket.setKeyed(keyed); @@ -166,7 +162,7 @@ public abstract class ParsedMultiBucketAggregation extends ParsedAggregation imp if (CommonFields.KEY_AS_STRING.getPreferredName().equals(currentFieldName)) { bucket.setKeyAsString(parser.text()); } else if (CommonFields.KEY.getPreferredName().equals(currentFieldName)) { - bucket.setKey(keyParser.apply(parser)); + keyConsumer.accept(parser, bucket); } else if (CommonFields.DOC_COUNT.getPreferredName().equals(currentFieldName)) { bucket.setDocCount(parser.longValue()); } diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/bucket/histogram/ParsedDateHistogram.java b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/histogram/ParsedDateHistogram.java index 27ba2c029d2..9efc478aff1 100644 --- a/core/src/main/java/org/elasticsearch/search/aggregations/bucket/histogram/ParsedDateHistogram.java +++ b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/histogram/ParsedDateHistogram.java @@ -20,17 +20,16 @@ package org.elasticsearch.search.aggregations.bucket.histogram; import org.elasticsearch.common.xcontent.ObjectParser; +import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentParser; -import org.elasticsearch.search.DocValueFormat; import org.elasticsearch.search.aggregations.ParsedMultiBucketAggregation; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; import java.io.IOException; import java.util.List; -import java.util.stream.Collectors; -public class ParsedDateHistogram extends ParsedMultiBucketAggregation implements Histogram { +public class ParsedDateHistogram extends ParsedMultiBucketAggregation implements Histogram { @Override protected String getType() { @@ -39,7 +38,7 @@ public class ParsedDateHistogram extends ParsedMultiBucketAggregation implements @Override public List getBuckets() { - return buckets.stream().map(bucket -> (Histogram.Bucket) bucket).collect(Collectors.toList()); + return buckets; } private static ObjectParser PARSER = @@ -56,11 +55,16 @@ public class ParsedDateHistogram extends ParsedMultiBucketAggregation implements return aggregation; } - public static class ParsedBucket extends ParsedMultiBucketAggregation.ParsedBucket implements Histogram.Bucket { + public static class ParsedBucket extends ParsedMultiBucketAggregation.ParsedBucket implements Histogram.Bucket { + + private Long key; @Override public Object getKey() { - return new DateTime(super.getKey(), DateTimeZone.UTC); + if (key != null) { + return new DateTime(key, DateTimeZone.UTC); + } + return null; } @Override @@ -68,13 +72,20 @@ public class ParsedDateHistogram extends ParsedMultiBucketAggregation implements String keyAsString = super.getKeyAsString(); if (keyAsString != null) { return keyAsString; - } else { - return DocValueFormat.RAW.format((Long) super.getKey()); } + if (key != null) { + return Long.toString(key); + } + return null; + } + + @Override + protected XContentBuilder keyToXContent(XContentBuilder builder) throws IOException { + return builder.field(CommonFields.KEY.getPreferredName(), key); } static ParsedBucket fromXContent(XContentParser parser, boolean keyed) throws IOException { - return parseXContent(parser, keyed, ParsedBucket::new, XContentParser::longValue); + return parseXContent(parser, keyed, ParsedBucket::new, (p, bucket) -> bucket.key = p.longValue()); } } } diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/bucket/histogram/ParsedHistogram.java b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/histogram/ParsedHistogram.java index 2b6730df2cc..eeec1cd81ba 100644 --- a/core/src/main/java/org/elasticsearch/search/aggregations/bucket/histogram/ParsedHistogram.java +++ b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/histogram/ParsedHistogram.java @@ -21,14 +21,12 @@ package org.elasticsearch.search.aggregations.bucket.histogram; import org.elasticsearch.common.xcontent.ObjectParser; import org.elasticsearch.common.xcontent.XContentParser; -import org.elasticsearch.search.DocValueFormat; import org.elasticsearch.search.aggregations.ParsedMultiBucketAggregation; import java.io.IOException; import java.util.List; -import java.util.stream.Collectors; -public class ParsedHistogram extends ParsedMultiBucketAggregation implements Histogram { +public class ParsedHistogram extends ParsedMultiBucketAggregation implements Histogram { @Override protected String getType() { @@ -37,7 +35,7 @@ public class ParsedHistogram extends ParsedMultiBucketAggregation implements His @Override public List getBuckets() { - return buckets.stream().map(bucket -> (Histogram.Bucket) bucket).collect(Collectors.toList()); + return buckets; } private static ObjectParser PARSER = @@ -54,20 +52,29 @@ public class ParsedHistogram extends ParsedMultiBucketAggregation implements His return aggregation; } - static class ParsedBucket extends ParsedMultiBucketAggregation.ParsedBucket implements Histogram.Bucket { + static class ParsedBucket extends ParsedMultiBucketAggregation.ParsedBucket implements Histogram.Bucket { + + private Double key; + + @Override + public Object getKey() { + return key; + } @Override public String getKeyAsString() { String keyAsString = super.getKeyAsString(); if (keyAsString != null) { return keyAsString; - } else { - return DocValueFormat.RAW.format((Double) getKey()); } + if (key != null) { + return Double.toString(key); + } + return null; } static ParsedBucket fromXContent(XContentParser parser, boolean keyed) throws IOException { - return parseXContent(parser, keyed, ParsedBucket::new, XContentParser::doubleValue); + return parseXContent(parser, keyed, ParsedBucket::new, (p, bucket) -> bucket.key = p.doubleValue()); } } } diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/bucket/terms/ParsedDoubleTerms.java b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/terms/ParsedDoubleTerms.java new file mode 100644 index 00000000000..3401cd021bd --- /dev/null +++ b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/terms/ParsedDoubleTerms.java @@ -0,0 +1,85 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.search.aggregations.bucket.terms; + +import org.elasticsearch.common.xcontent.ObjectParser; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentParser; + +import java.io.IOException; + +public class ParsedDoubleTerms extends ParsedTerms { + + @Override + protected String getType() { + return DoubleTerms.NAME; + } + + private static ObjectParser PARSER = + new ObjectParser<>(ParsedDoubleTerms.class.getSimpleName(), true, ParsedDoubleTerms::new); + static { + declareParsedTermsFields(PARSER, ParsedBucket::fromXContent); + } + + public static ParsedDoubleTerms fromXContent(XContentParser parser, String name) throws IOException { + ParsedDoubleTerms aggregation = PARSER.parse(parser, null); + aggregation.setName(name); + return aggregation; + } + + public static class ParsedBucket extends ParsedTerms.ParsedBucket { + + private Double key; + + @Override + public Object getKey() { + return key; + } + + @Override + public String getKeyAsString() { + String keyAsString = super.getKeyAsString(); + if (keyAsString != null) { + return keyAsString; + } + if (key != null) { + return Double.toString(key); + } + return null; + } + + public Number getKeyAsNumber() { + return key; + } + + @Override + protected XContentBuilder keyToXContent(XContentBuilder builder) throws IOException { + builder.field(CommonFields.KEY.getPreferredName(), key); + if (super.getKeyAsString() != null) { + builder.field(CommonFields.KEY_AS_STRING.getPreferredName(), getKeyAsString()); + } + return builder; + } + + static ParsedBucket fromXContent(XContentParser parser) throws IOException { + return parseTermsBucketXContent(parser, ParsedBucket::new, (p, bucket) -> bucket.key = p.doubleValue()); + } + } +} diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/bucket/terms/ParsedLongTerms.java b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/terms/ParsedLongTerms.java new file mode 100644 index 00000000000..6de25dfefe1 --- /dev/null +++ b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/terms/ParsedLongTerms.java @@ -0,0 +1,85 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.search.aggregations.bucket.terms; + +import org.elasticsearch.common.xcontent.ObjectParser; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentParser; + +import java.io.IOException; + +public class ParsedLongTerms extends ParsedTerms { + + @Override + protected String getType() { + return LongTerms.NAME; + } + + private static ObjectParser PARSER = + new ObjectParser<>(ParsedLongTerms.class.getSimpleName(), true, ParsedLongTerms::new); + static { + declareParsedTermsFields(PARSER, ParsedBucket::fromXContent); + } + + public static ParsedLongTerms fromXContent(XContentParser parser, String name) throws IOException { + ParsedLongTerms aggregation = PARSER.parse(parser, null); + aggregation.setName(name); + return aggregation; + } + + public static class ParsedBucket extends ParsedTerms.ParsedBucket { + + private Long key; + + @Override + public Object getKey() { + return key; + } + + @Override + public String getKeyAsString() { + String keyAsString = super.getKeyAsString(); + if (keyAsString != null) { + return keyAsString; + } + if (key != null) { + return Long.toString(key); + } + return null; + } + + public Number getKeyAsNumber() { + return key; + } + + @Override + protected XContentBuilder keyToXContent(XContentBuilder builder) throws IOException { + builder.field(CommonFields.KEY.getPreferredName(), key); + if (super.getKeyAsString() != null) { + builder.field(CommonFields.KEY_AS_STRING.getPreferredName(), getKeyAsString()); + } + return builder; + } + + static ParsedBucket fromXContent(XContentParser parser) throws IOException { + return parseTermsBucketXContent(parser, ParsedBucket::new, (p, bucket) -> bucket.key = p.longValue()); + } + } +} diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/bucket/terms/ParsedStringTerms.java b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/terms/ParsedStringTerms.java new file mode 100644 index 00000000000..f258f7f847e --- /dev/null +++ b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/terms/ParsedStringTerms.java @@ -0,0 +1,85 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.search.aggregations.bucket.terms; + +import org.apache.lucene.util.BytesRef; +import org.elasticsearch.common.xcontent.ObjectParser; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentParser; + +import java.io.IOException; + +public class ParsedStringTerms extends ParsedTerms { + + @Override + protected String getType() { + return StringTerms.NAME; + } + + private static ObjectParser PARSER = + new ObjectParser<>(ParsedStringTerms.class.getSimpleName(), true, ParsedStringTerms::new); + static { + declareParsedTermsFields(PARSER, ParsedBucket::fromXContent); + } + + public static ParsedStringTerms fromXContent(XContentParser parser, String name) throws IOException { + ParsedStringTerms aggregation = PARSER.parse(parser, null); + aggregation.setName(name); + return aggregation; + } + + public static class ParsedBucket extends ParsedTerms.ParsedBucket { + + private BytesRef key; + + @Override + public Object getKey() { + return getKeyAsString(); + } + + @Override + public String getKeyAsString() { + String keyAsString = super.getKeyAsString(); + if (keyAsString != null) { + return keyAsString; + } + if (key != null) { + return key.utf8ToString(); + } + return null; + } + + public Number getKeyAsNumber() { + if (key != null) { + return Double.parseDouble(key.utf8ToString()); + } + return null; + } + + @Override + protected XContentBuilder keyToXContent(XContentBuilder builder) throws IOException { + return builder.field(CommonFields.KEY.getPreferredName(), getKey()); + } + + static ParsedBucket fromXContent(XContentParser parser) throws IOException { + return parseTermsBucketXContent(parser, ParsedBucket::new, (p, bucket) -> bucket.key = p.utf8BytesOrNull()); + } + } +} diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/bucket/terms/ParsedTerms.java b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/terms/ParsedTerms.java new file mode 100644 index 00000000000..6e24bd00383 --- /dev/null +++ b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/terms/ParsedTerms.java @@ -0,0 +1,151 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.search.aggregations.bucket.terms; + +import org.elasticsearch.common.CheckedBiConsumer; +import org.elasticsearch.common.CheckedFunction; +import org.elasticsearch.common.xcontent.ObjectParser; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.common.xcontent.XContentParserUtils; +import org.elasticsearch.search.aggregations.Aggregation; +import org.elasticsearch.search.aggregations.Aggregations; +import org.elasticsearch.search.aggregations.ParsedMultiBucketAggregation; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Supplier; + +import static org.elasticsearch.search.aggregations.bucket.terms.InternalTerms.DOC_COUNT_ERROR_UPPER_BOUND_FIELD_NAME; +import static org.elasticsearch.search.aggregations.bucket.terms.InternalTerms.SUM_OF_OTHER_DOC_COUNTS; + +public abstract class ParsedTerms extends ParsedMultiBucketAggregation implements Terms { + + protected long docCountErrorUpperBound; + protected long sumOtherDocCount; + + @Override + public long getDocCountError() { + return docCountErrorUpperBound; + } + + @Override + public long getSumOfOtherDocCounts() { + return sumOtherDocCount; + } + + @Override + public List getBuckets() { + return buckets; + } + + @Override + public Terms.Bucket getBucketByKey(String term) { + for (Terms.Bucket bucket : getBuckets()) { + if (bucket.getKeyAsString().equals(term)) { + return bucket; + } + } + return null; + } + + @Override + protected XContentBuilder doXContentBody(XContentBuilder builder, Params params) throws IOException { + builder.field(DOC_COUNT_ERROR_UPPER_BOUND_FIELD_NAME.getPreferredName(), getDocCountError()); + builder.field(SUM_OF_OTHER_DOC_COUNTS.getPreferredName(), getSumOfOtherDocCounts()); + builder.startArray(CommonFields.BUCKETS.getPreferredName()); + for (Terms.Bucket bucket : getBuckets()) { + bucket.toXContent(builder, params); + } + builder.endArray(); + return builder; + } + + static void declareParsedTermsFields(final ObjectParser objectParser, + final CheckedFunction bucketParser) { + declareMultiBucketAggregationFields(objectParser, bucketParser::apply, bucketParser::apply); + objectParser.declareLong((parsedTerms, value) -> parsedTerms.docCountErrorUpperBound = value , + DOC_COUNT_ERROR_UPPER_BOUND_FIELD_NAME); + objectParser.declareLong((parsedTerms, value) -> parsedTerms.sumOtherDocCount = value, + SUM_OF_OTHER_DOC_COUNTS); + } + + public abstract static class ParsedBucket extends ParsedMultiBucketAggregation.ParsedBucket implements Terms.Bucket { + + boolean showDocCountError = false; + protected long docCountError; + + @Override + public int compareTerm(Terms.Bucket other) { + throw new UnsupportedOperationException(); + } + + @Override + public long getDocCountError() { + return docCountError; + } + + @Override + public final XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(); + keyToXContent(builder); + builder.field(CommonFields.DOC_COUNT.getPreferredName(), getDocCount()); + if (showDocCountError) { + builder.field(DOC_COUNT_ERROR_UPPER_BOUND_FIELD_NAME.getPreferredName(), getDocCountError()); + } + getAggregations().toXContentInternal(builder, params); + builder.endObject(); + return builder; + } + + + static B parseTermsBucketXContent(final XContentParser parser, final Supplier bucketSupplier, + final CheckedBiConsumer keyConsumer) + throws IOException { + + final B bucket = bucketSupplier.get(); + final List aggregations = new ArrayList<>(); + + XContentParser.Token token; + String currentFieldName = parser.currentName(); + while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { + if (token == XContentParser.Token.FIELD_NAME) { + currentFieldName = parser.currentName(); + } else if (token.isValue()) { + if (CommonFields.KEY_AS_STRING.getPreferredName().equals(currentFieldName)) { + bucket.setKeyAsString(parser.text()); + } else if (CommonFields.KEY.getPreferredName().equals(currentFieldName)) { + keyConsumer.accept(parser, bucket); + } else if (CommonFields.DOC_COUNT.getPreferredName().equals(currentFieldName)) { + bucket.setDocCount(parser.longValue()); + } else if (DOC_COUNT_ERROR_UPPER_BOUND_FIELD_NAME.getPreferredName().equals(currentFieldName)) { + bucket.docCountError = parser.longValue(); + bucket.showDocCountError = true; + } + } else if (token == XContentParser.Token.START_OBJECT) { + aggregations.add(XContentParserUtils.parseTypedKeysObject(parser, Aggregation.TYPED_KEYS_DELIMITER, Aggregation.class)); + } + } + bucket.setAggregations(new Aggregations(aggregations)); + return bucket; + } + } +} diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/metrics/percentiles/ParsedPercentiles.java b/core/src/main/java/org/elasticsearch/search/aggregations/metrics/percentiles/ParsedPercentiles.java index a96fb2cd13c..48f3dccecef 100644 --- a/core/src/main/java/org/elasticsearch/search/aggregations/metrics/percentiles/ParsedPercentiles.java +++ b/core/src/main/java/org/elasticsearch/search/aggregations/metrics/percentiles/ParsedPercentiles.java @@ -22,7 +22,6 @@ package org.elasticsearch.search.aggregations.metrics.percentiles; import org.elasticsearch.common.xcontent.ObjectParser; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentParser; -import org.elasticsearch.search.DocValueFormat; import org.elasticsearch.search.aggregations.ParsedAggregation; import java.io.IOException; @@ -60,7 +59,7 @@ public abstract class ParsedPercentiles extends ParsedAggregation implements Ite } Double value = getPercentile(percent); if (value != null) { - return DocValueFormat.RAW.format(value); + return Double.toString(value); } return null; } diff --git a/core/src/test/java/org/elasticsearch/search/aggregations/InternalAggregationTestCase.java b/core/src/test/java/org/elasticsearch/search/aggregations/InternalAggregationTestCase.java index 782e98e8885..a766dcbf5c8 100644 --- a/core/src/test/java/org/elasticsearch/search/aggregations/InternalAggregationTestCase.java +++ b/core/src/test/java/org/elasticsearch/search/aggregations/InternalAggregationTestCase.java @@ -28,6 +28,7 @@ import org.elasticsearch.common.xcontent.ContextParser; import org.elasticsearch.common.xcontent.NamedXContentRegistry; import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.common.xcontent.XContentParserUtils; import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.indices.breaker.NoneCircuitBreakerService; import org.elasticsearch.rest.action.search.RestSearchAction; @@ -38,6 +39,12 @@ import org.elasticsearch.search.aggregations.bucket.histogram.DateHistogramAggre import org.elasticsearch.search.aggregations.bucket.histogram.HistogramAggregationBuilder; import org.elasticsearch.search.aggregations.bucket.histogram.ParsedDateHistogram; import org.elasticsearch.search.aggregations.bucket.histogram.ParsedHistogram; +import org.elasticsearch.search.aggregations.bucket.terms.DoubleTerms; +import org.elasticsearch.search.aggregations.bucket.terms.LongTerms; +import org.elasticsearch.search.aggregations.bucket.terms.ParsedDoubleTerms; +import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms; +import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms; +import org.elasticsearch.search.aggregations.bucket.terms.StringTerms; import org.elasticsearch.search.aggregations.metrics.avg.AvgAggregationBuilder; import org.elasticsearch.search.aggregations.metrics.avg.ParsedAvg; import org.elasticsearch.search.aggregations.metrics.cardinality.CardinalityAggregationBuilder; @@ -127,6 +134,9 @@ public abstract class InternalAggregationTestCase namedXContents.put(GeoCentroidAggregationBuilder.NAME, (p, c) -> ParsedGeoCentroid.fromXContent(p, (String) c)); namedXContents.put(HistogramAggregationBuilder.NAME, (p, c) -> ParsedHistogram.fromXContent(p, (String) c)); namedXContents.put(DateHistogramAggregationBuilder.NAME, (p, c) -> ParsedDateHistogram.fromXContent(p, (String) c)); + namedXContents.put(StringTerms.NAME, (p, c) -> ParsedStringTerms.fromXContent(p, (String) c)); + namedXContents.put(LongTerms.NAME, (p, c) -> ParsedLongTerms.fromXContent(p, (String) c)); + namedXContents.put(DoubleTerms.NAME, (p, c) -> ParsedDoubleTerms.fromXContent(p, (String) c)); return namedXContents.entrySet().stream() .map(entry -> new NamedXContentRegistry.Entry(Aggregation.class, new ParseField(entry.getKey()), entry.getValue())) @@ -259,12 +269,7 @@ public abstract class InternalAggregationTestCase assertEquals(XContentParser.Token.START_OBJECT, parser.nextToken()); assertEquals(XContentParser.Token.FIELD_NAME, parser.nextToken()); - String currentName = parser.currentName(); - int i = currentName.indexOf(InternalAggregation.TYPED_KEYS_DELIMITER); - String aggType = currentName.substring(0, i); - String aggName = currentName.substring(i + 1); - - parsedAggregation = parser.namedObject(Aggregation.class, aggType, aggName); + parsedAggregation = XContentParserUtils.parseTypedKeysObject(parser, Aggregation.TYPED_KEYS_DELIMITER, Aggregation.class); assertEquals(XContentParser.Token.END_OBJECT, parser.currentToken()); assertEquals(XContentParser.Token.END_OBJECT, parser.nextToken()); @@ -277,7 +282,7 @@ public abstract class InternalAggregationTestCase assertEquals(aggregation.getType(), ((ParsedAggregation) parsedAggregation).getType()); } - BytesReference parsedBytes = toXContent((ToXContent) parsedAggregation, xContentType, params, humanReadable); + BytesReference parsedBytes = toXContent(parsedAggregation, xContentType, params, humanReadable); assertToXContentEquivalent(originalBytes, parsedBytes, xContentType); return (P) parsedAggregation; diff --git a/core/src/test/java/org/elasticsearch/search/aggregations/bucket/terms/DoubleTermsTests.java b/core/src/test/java/org/elasticsearch/search/aggregations/bucket/terms/DoubleTermsTests.java index 757d5647a12..45531e27dde 100644 --- a/core/src/test/java/org/elasticsearch/search/aggregations/bucket/terms/DoubleTermsTests.java +++ b/core/src/test/java/org/elasticsearch/search/aggregations/bucket/terms/DoubleTermsTests.java @@ -22,6 +22,7 @@ package org.elasticsearch.search.aggregations.bucket.terms; import org.elasticsearch.common.io.stream.Writeable.Reader; import org.elasticsearch.search.DocValueFormat; import org.elasticsearch.search.aggregations.InternalAggregations; +import org.elasticsearch.search.aggregations.ParsedMultiBucketAggregation; import org.elasticsearch.search.aggregations.pipeline.PipelineAggregator; import java.util.ArrayList; @@ -33,17 +34,17 @@ import java.util.Set; public class DoubleTermsTests extends InternalTermsTestCase { @Override - protected InternalTerms createTestInstance( - String name, - List pipelineAggregators, - Map metaData) { + protected InternalTerms createTestInstance(String name, + List pipelineAggregators, + Map metaData, + InternalAggregations aggregations, + boolean showTermDocCountError, + long docCountError) { Terms.Order order = Terms.Order.count(false); long minDocCount = 1; int requiredSize = 3; int shardSize = requiredSize + 2; - DocValueFormat format = DocValueFormat.RAW; - boolean showTermDocCountError = false; - long docCountError = -1; + DocValueFormat format = randomNumericDocValueFormat(); long otherDocCount = 0; List buckets = new ArrayList<>(); final int numBuckets = randomInt(shardSize); @@ -51,8 +52,7 @@ public class DoubleTermsTests extends InternalTermsTestCase { for (int i = 0; i < numBuckets; ++i) { double term = randomValueOtherThanMany(d -> terms.add(d) == false, random()::nextDouble); int docCount = randomIntBetween(1, 100); - buckets.add(new DoubleTerms.Bucket(term, docCount, InternalAggregations.EMPTY, - showTermDocCountError, docCountError, format)); + buckets.add(new DoubleTerms.Bucket(term, docCount, aggregations, showTermDocCountError, docCountError, format)); } return new DoubleTerms(name, order, requiredSize, minDocCount, pipelineAggregators, metaData, format, shardSize, showTermDocCountError, otherDocCount, buckets, docCountError); @@ -63,4 +63,9 @@ public class DoubleTermsTests extends InternalTermsTestCase { return DoubleTerms::new; } + @Override + protected Class implementationClass() { + return ParsedDoubleTerms.class; + } + } diff --git a/core/src/test/java/org/elasticsearch/search/aggregations/bucket/terms/InternalTermsTestCase.java b/core/src/test/java/org/elasticsearch/search/aggregations/bucket/terms/InternalTermsTestCase.java index 03031633f74..cfd7a82da1a 100644 --- a/core/src/test/java/org/elasticsearch/search/aggregations/bucket/terms/InternalTermsTestCase.java +++ b/core/src/test/java/org/elasticsearch/search/aggregations/bucket/terms/InternalTermsTestCase.java @@ -19,18 +19,44 @@ package org.elasticsearch.search.aggregations.bucket.terms; -import org.elasticsearch.search.aggregations.InternalAggregationTestCase; +import org.elasticsearch.search.aggregations.InternalAggregations; +import org.elasticsearch.search.aggregations.InternalMultiBucketAggregationTestCase; import org.elasticsearch.search.aggregations.pipeline.PipelineAggregator; +import org.junit.Before; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.Map.Entry; +import java.util.Optional; import java.util.stream.Collectors; import java.util.stream.Stream; -public abstract class InternalTermsTestCase extends InternalAggregationTestCase> { +public abstract class InternalTermsTestCase extends InternalMultiBucketAggregationTestCase> { + + private boolean showDocCount; + private long docCountError; + + @Before + public void init() { + showDocCount = randomBoolean(); + docCountError = showDocCount ? randomInt(1000) : -1; + } + + @Override + protected InternalTerms createTestInstance(String name, + List pipelineAggregators, + Map metaData, + InternalAggregations aggregations) { + return createTestInstance(name, pipelineAggregators, metaData, aggregations, showDocCount, docCountError); + } + + protected abstract InternalTerms createTestInstance(String name, + List pipelineAggregators, + Map metaData, + InternalAggregations aggregations, + boolean showTermDocCountError, + long docCountError); @Override protected InternalTerms createUnmappedInstance( diff --git a/core/src/test/java/org/elasticsearch/search/aggregations/bucket/terms/LongTermsTests.java b/core/src/test/java/org/elasticsearch/search/aggregations/bucket/terms/LongTermsTests.java index ff95984bc32..cc97e4989a9 100644 --- a/core/src/test/java/org/elasticsearch/search/aggregations/bucket/terms/LongTermsTests.java +++ b/core/src/test/java/org/elasticsearch/search/aggregations/bucket/terms/LongTermsTests.java @@ -22,6 +22,7 @@ package org.elasticsearch.search.aggregations.bucket.terms; import org.elasticsearch.common.io.stream.Writeable.Reader; import org.elasticsearch.search.DocValueFormat; import org.elasticsearch.search.aggregations.InternalAggregations; +import org.elasticsearch.search.aggregations.ParsedMultiBucketAggregation; import org.elasticsearch.search.aggregations.pipeline.PipelineAggregator; import java.util.ArrayList; @@ -33,17 +34,17 @@ import java.util.Set; public class LongTermsTests extends InternalTermsTestCase { @Override - protected InternalTerms createTestInstance( - String name, - List pipelineAggregators, - Map metaData) { + protected InternalTerms createTestInstance(String name, + List pipelineAggregators, + Map metaData, + InternalAggregations aggregations, + boolean showTermDocCountError, + long docCountError) { Terms.Order order = Terms.Order.count(false); long minDocCount = 1; int requiredSize = 3; int shardSize = requiredSize + 2; - DocValueFormat format = DocValueFormat.RAW; - boolean showTermDocCountError = false; - long docCountError = -1; + DocValueFormat format = randomNumericDocValueFormat(); long otherDocCount = 0; List buckets = new ArrayList<>(); final int numBuckets = randomInt(shardSize); @@ -51,8 +52,7 @@ public class LongTermsTests extends InternalTermsTestCase { for (int i = 0; i < numBuckets; ++i) { long term = randomValueOtherThanMany(l -> terms.add(l) == false, random()::nextLong); int docCount = randomIntBetween(1, 100); - buckets.add(new LongTerms.Bucket(term, docCount, InternalAggregations.EMPTY, - showTermDocCountError, docCountError, format)); + buckets.add(new LongTerms.Bucket(term, docCount, aggregations, showTermDocCountError, docCountError, format)); } return new LongTerms(name, order, requiredSize, minDocCount, pipelineAggregators, metaData, format, shardSize, showTermDocCountError, otherDocCount, buckets, docCountError); @@ -63,4 +63,8 @@ public class LongTermsTests extends InternalTermsTestCase { return LongTerms::new; } + @Override + protected Class implementationClass() { + return ParsedLongTerms.class; + } } diff --git a/core/src/test/java/org/elasticsearch/search/aggregations/bucket/terms/StringTermsTests.java b/core/src/test/java/org/elasticsearch/search/aggregations/bucket/terms/StringTermsTests.java index 64e814bd819..e909358be5e 100644 --- a/core/src/test/java/org/elasticsearch/search/aggregations/bucket/terms/StringTermsTests.java +++ b/core/src/test/java/org/elasticsearch/search/aggregations/bucket/terms/StringTermsTests.java @@ -23,6 +23,7 @@ import org.apache.lucene.util.BytesRef; import org.elasticsearch.common.io.stream.Writeable.Reader; import org.elasticsearch.search.DocValueFormat; import org.elasticsearch.search.aggregations.InternalAggregations; +import org.elasticsearch.search.aggregations.ParsedMultiBucketAggregation; import org.elasticsearch.search.aggregations.pipeline.PipelineAggregator; import java.util.ArrayList; @@ -34,17 +35,17 @@ import java.util.Set; public class StringTermsTests extends InternalTermsTestCase { @Override - protected InternalTerms createTestInstance( - String name, - List pipelineAggregators, - Map metaData) { + protected InternalTerms createTestInstance(String name, + List pipelineAggregators, + Map metaData, + InternalAggregations aggregations, + boolean showTermDocCountError, + long docCountError) { Terms.Order order = Terms.Order.count(false); long minDocCount = 1; int requiredSize = 3; int shardSize = requiredSize + 2; DocValueFormat format = DocValueFormat.RAW; - boolean showTermDocCountError = false; - long docCountError = -1; long otherDocCount = 0; List buckets = new ArrayList<>(); final int numBuckets = randomInt(shardSize); @@ -52,8 +53,7 @@ public class StringTermsTests extends InternalTermsTestCase { for (int i = 0; i < numBuckets; ++i) { BytesRef term = randomValueOtherThanMany(b -> terms.add(b) == false, () -> new BytesRef(randomAlphaOfLength(10))); int docCount = randomIntBetween(1, 100); - buckets.add(new StringTerms.Bucket(term, docCount, InternalAggregations.EMPTY, - showTermDocCountError, docCountError, format)); + buckets.add(new StringTerms.Bucket(term, docCount, aggregations, showTermDocCountError, docCountError, format)); } return new StringTerms(name, order, requiredSize, minDocCount, pipelineAggregators, metaData, format, shardSize, showTermDocCountError, otherDocCount, buckets, docCountError); @@ -64,4 +64,8 @@ public class StringTermsTests extends InternalTermsTestCase { return StringTerms::new; } + @Override + protected Class implementationClass() { + return ParsedStringTerms.class; + } }