From 6ef3c7fa604a841bd56fdfdb9c15a3f131e8d5e7 Mon Sep 17 00:00:00 2001 From: Jim Ferenczi Date: Mon, 7 Jan 2019 10:00:31 +0100 Subject: [PATCH 1/2] Allow nested fields in the composite aggregation This changes adds the support to handle `nested` fields in the `composite` aggregation. A `nested` aggregation can be used as parent of a `composite` aggregation in order to target `nested` fields in the `sources`. Closes #28611 --- .../bucket/composite-aggregation.asciidoc | 18 ++++- .../test/search.aggregation/230_composite.yml | 77 ++++++++++++++++++- .../CompositeAggregationBuilder.java | 22 +++++- .../nested/NestedAggregatorFactory.java | 2 +- 4 files changed, 111 insertions(+), 8 deletions(-) diff --git a/docs/reference/aggregations/bucket/composite-aggregation.asciidoc b/docs/reference/aggregations/bucket/composite-aggregation.asciidoc index 1a63f29467d4b..66525b8c3e785 100644 --- a/docs/reference/aggregations/bucket/composite-aggregation.asciidoc +++ b/docs/reference/aggregations/bucket/composite-aggregation.asciidoc @@ -32,6 +32,23 @@ PUT /sales }, "shop": { "type": "keyword" + }, + "nested": { + "type": "nested", + "properties": { + "product": { + "type": "keyword" + }, + "timestamp": { + "type": "date" + }, + "price": { + "type": "long" + }, + "shop": { + "type": "keyword" + } + } } } } @@ -289,7 +306,6 @@ GET /_search -------------------------------------------------- // CONSOLE - This will create composite buckets from the values created by two values source, a `date_histogram` and a `terms`. Each bucket is composed of two values, one for each value source defined in the aggregation. Any type of combinations is allowed and the order in the array is preserved diff --git a/rest-api-spec/src/main/resources/rest-api-spec/test/search.aggregation/230_composite.yml b/rest-api-spec/src/main/resources/rest-api-spec/test/search.aggregation/230_composite.yml index 325bdf8f18e22..73bf44cb5d589 100644 --- a/rest-api-spec/src/main/resources/rest-api-spec/test/search.aggregation/230_composite.yml +++ b/rest-api-spec/src/main/resources/rest-api-spec/test/search.aggregation/230_composite.yml @@ -13,13 +13,18 @@ setup: type: keyword long: type: long + nested: + type: nested + properties: + nested_long: + type: long - do: index: index: test type: doc id: 1 - body: { "keyword": "foo", "long": [10, 20] } + body: { "keyword": "foo", "long": [10, 20], "nested": [{"nested_long": 10}, {"nested_long": 20}] } - do: index: @@ -33,14 +38,14 @@ setup: index: test type: doc id: 3 - body: { "keyword": "bar", "long": [100, 0] } + body: { "keyword": "bar", "long": [100, 0], "nested": [{"nested_long": 10}, {"nested_long": 0}] } - do: index: index: test type: doc id: 4 - body: { "keyword": "bar", "long": [1000, 0] } + body: { "keyword": "bar", "long": [1000, 0], "nested": [{"nested_long": 1000}, {"nested_long": 20}] } - do: index: @@ -66,7 +71,6 @@ setup: version: " - 6.0.99" reason: this uses a new API that has been added in 6.1 - - do: search: rest_total_hits_as_int: true @@ -357,3 +361,68 @@ setup: } } ] + +--- +"Composite aggregation with nested parent": + - skip: + version: " - 6.99.99" + reason: the ability to set a nested parent aggregation was added in 7.0. + + - do: + search: + rest_total_hits_as_int: true + index: test + body: + aggregations: + 1: + nested: + path: nested + aggs: + 2: + composite: + sources: [ + "nested": { + "terms": { + "field": "nested.nested_long" + } + } + ] + + - match: {hits.total: 6} + - length: { aggregations.1.2.buckets: 4 } + - match: { aggregations.1.2.buckets.0.key.nested: 0 } + - match: { aggregations.1.2.buckets.0.doc_count: 1 } + - match: { aggregations.1.2.buckets.1.key.nested: 10 } + - match: { aggregations.1.2.buckets.1.doc_count: 2 } + - match: { aggregations.1.2.buckets.2.key.nested: 20 } + - match: { aggregations.1.2.buckets.2.doc_count: 2 } + - match: { aggregations.1.2.buckets.3.key.nested: 1000 } + - match: { aggregations.1.2.buckets.3.doc_count: 1 } + + - do: + search: + rest_total_hits_as_int: true + index: test + body: + aggregations: + 1: + nested: + path: nested + aggs: + 2: + composite: + after: { "nested": 10 } + sources: [ + "nested": { + "terms": { + "field": "nested.nested_long" + } + } + ] + + - match: {hits.total: 6} + - length: { aggregations.1.2.buckets: 2 } + - match: { aggregations.1.2.buckets.0.key.nested: 20 } + - match: { aggregations.1.2.buckets.0.doc_count: 2 } + - match: { aggregations.1.2.buckets.1.key.nested: 1000 } + - match: { aggregations.1.2.buckets.1.doc_count: 1 } diff --git a/server/src/main/java/org/elasticsearch/search/aggregations/bucket/composite/CompositeAggregationBuilder.java b/server/src/main/java/org/elasticsearch/search/aggregations/bucket/composite/CompositeAggregationBuilder.java index 43e33fad93189..69910d21ed8ad 100644 --- a/server/src/main/java/org/elasticsearch/search/aggregations/bucket/composite/CompositeAggregationBuilder.java +++ b/server/src/main/java/org/elasticsearch/search/aggregations/bucket/composite/CompositeAggregationBuilder.java @@ -29,6 +29,7 @@ import org.elasticsearch.search.aggregations.AggregationBuilder; import org.elasticsearch.search.aggregations.AggregatorFactories; import org.elasticsearch.search.aggregations.AggregatorFactory; +import org.elasticsearch.search.aggregations.bucket.nested.NestedAggregatorFactory; import org.elasticsearch.search.internal.SearchContext; import java.io.IOException; @@ -151,11 +152,28 @@ public int size() { return size; } + /** + * Returns null if the provided factory and his parents are compatible with + * this aggregator or the instance of the parent's factory that is incompatible with + * the composite aggregation. + */ + private AggregatorFactory checkParentIsNullOrNested(AggregatorFactory factory) { + if (factory == null) { + return null; + } else if (factory instanceof NestedAggregatorFactory) { + return checkParentIsNullOrNested(factory.getParent()); + } else { + return factory; + } + } + @Override protected AggregatorFactory doBuild(SearchContext context, AggregatorFactory parent, AggregatorFactories.Builder subfactoriesBuilder) throws IOException { - if (parent != null) { - throw new IllegalArgumentException("[composite] aggregation cannot be used with a parent aggregation"); + AggregatorFactory invalid = checkParentIsNullOrNested(parent); + if (invalid != null) { + throw new IllegalArgumentException("[composite] aggregation cannot be used with a parent aggregation of" + + " type: [" + invalid.getClass().getSimpleName() + "]"); } CompositeValuesSourceConfig[] configs = new CompositeValuesSourceConfig[sources.size()]; for (int i = 0; i < configs.length; i++) { diff --git a/server/src/main/java/org/elasticsearch/search/aggregations/bucket/nested/NestedAggregatorFactory.java b/server/src/main/java/org/elasticsearch/search/aggregations/bucket/nested/NestedAggregatorFactory.java index dfbe18ba87b4f..6724ee7da30d6 100644 --- a/server/src/main/java/org/elasticsearch/search/aggregations/bucket/nested/NestedAggregatorFactory.java +++ b/server/src/main/java/org/elasticsearch/search/aggregations/bucket/nested/NestedAggregatorFactory.java @@ -32,7 +32,7 @@ import java.util.List; import java.util.Map; -class NestedAggregatorFactory extends AggregatorFactory { +public class NestedAggregatorFactory extends AggregatorFactory { private final ObjectMapper parentObjectMapper; private final ObjectMapper childObjectMapper; From f73d8ed143baf4668630d0dd581799c03eb001ee Mon Sep 17 00:00:00 2001 From: Jim Ferenczi Date: Fri, 25 Jan 2019 11:02:48 +0100 Subject: [PATCH 2/2] fix docs after merge --- .../reference/aggregations/bucket/composite-aggregation.asciidoc | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/reference/aggregations/bucket/composite-aggregation.asciidoc b/docs/reference/aggregations/bucket/composite-aggregation.asciidoc index 1ebf079dbc10e..6d09379e16993 100644 --- a/docs/reference/aggregations/bucket/composite-aggregation.asciidoc +++ b/docs/reference/aggregations/bucket/composite-aggregation.asciidoc @@ -47,6 +47,7 @@ PUT /sales "shop": { "type": "keyword" } + } } } }