From b147bf3ab5ae0f701378ce3e983337cf343add58 Mon Sep 17 00:00:00 2001 From: Colin Goodheart-Smithe Date: Fri, 11 Dec 2015 17:21:13 +0000 Subject: [PATCH] Aggregations Refactor: Refactor Range Aggregations --- .../elasticsearch/common/network/Cidrs.java | 4 + .../bucket/range/InternalRange.java | 10 + .../bucket/range/RangeAggregator.java | 208 ++++++++++- .../bucket/range/RangeParser.java | 125 +++---- .../date/DateRangeAggregatorFactory.java | 51 +++ .../bucket/range/date/DateRangeParser.java | 105 ++---- .../bucket/range/date/InternalDateRange.java | 6 + .../range/geodistance/GeoDistanceParser.java | 330 ++++++++++++------ .../geodistance/InternalGeoDistance.java | 12 + .../ipv4/IPv4RangeAggregatorFactory.java | 197 +++++++++++ .../bucket/range/ipv4/InternalIPv4Range.java | 8 +- .../bucket/range/ipv4/IpRangeParser.java | 128 ++----- .../aggregations/support/GeoPointParser.java | 42 +-- .../aggregations/bucket/DateRangeTests.java | 66 ++++ .../bucket/GeoDistanceRangeTests.java | 68 ++++ .../aggregations/bucket/IPv4RangeTests.java | 75 ++++ .../aggregations/bucket/RangeTests.java | 67 ++++ 17 files changed, 1095 insertions(+), 407 deletions(-) create mode 100644 core/src/main/java/org/elasticsearch/search/aggregations/bucket/range/date/DateRangeAggregatorFactory.java create mode 100644 core/src/main/java/org/elasticsearch/search/aggregations/bucket/range/ipv4/IPv4RangeAggregatorFactory.java create mode 100644 core/src/test/java/org/elasticsearch/search/aggregations/bucket/DateRangeTests.java create mode 100644 core/src/test/java/org/elasticsearch/search/aggregations/bucket/GeoDistanceRangeTests.java create mode 100644 core/src/test/java/org/elasticsearch/search/aggregations/bucket/IPv4RangeTests.java create mode 100644 core/src/test/java/org/elasticsearch/search/aggregations/bucket/RangeTests.java diff --git a/core/src/main/java/org/elasticsearch/common/network/Cidrs.java b/core/src/main/java/org/elasticsearch/common/network/Cidrs.java index d0557248a6841..f0bd4fb74c861 100644 --- a/core/src/main/java/org/elasticsearch/common/network/Cidrs.java +++ b/core/src/main/java/org/elasticsearch/common/network/Cidrs.java @@ -113,4 +113,8 @@ static String octetsToCIDR(int[] octets, int networkMask) { assert octets.length == 4; return octetsToString(octets) + "/" + networkMask; } + + public static String createCIDR(long ipAddress, int networkMask) { + return octetsToCIDR(longToOctets(ipAddress), networkMask); + } } diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/bucket/range/InternalRange.java b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/range/InternalRange.java index 4aaf403145fdc..d96e8605361ba 100644 --- a/core/src/main/java/org/elasticsearch/search/aggregations/bucket/range/InternalRange.java +++ b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/range/InternalRange.java @@ -29,6 +29,8 @@ import org.elasticsearch.search.aggregations.bucket.BucketStreamContext; import org.elasticsearch.search.aggregations.bucket.BucketStreams; import org.elasticsearch.search.aggregations.pipeline.PipelineAggregator; +import org.elasticsearch.search.aggregations.support.ValueType; +import org.elasticsearch.search.aggregations.support.ValuesSourceType; import org.elasticsearch.search.aggregations.support.format.ValueFormatter; import org.elasticsearch.search.aggregations.support.format.ValueFormatterStreams; @@ -225,6 +227,14 @@ public Type type() { return TYPE; } + public ValuesSourceType getValueSourceType() { + return ValuesSourceType.NUMERIC; + } + + public ValueType getValueType() { + return ValueType.NUMERIC; + } + public R create(String name, List ranges, ValueFormatter formatter, boolean keyed, List pipelineAggregators, Map metaData) { return (R) new InternalRange<>(name, ranges, formatter, keyed, pipelineAggregators, metaData); diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/bucket/range/RangeAggregator.java b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/range/RangeAggregator.java index 2e6dc7138e8a2..7b079dec3e6bf 100644 --- a/core/src/main/java/org/elasticsearch/search/aggregations/bucket/range/RangeAggregator.java +++ b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/range/RangeAggregator.java @@ -20,6 +20,14 @@ import org.apache.lucene.index.LeafReaderContext; import org.apache.lucene.util.InPlaceMergeSorter; +import org.elasticsearch.common.ParseField; +import org.elasticsearch.common.ParseFieldMatcher; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.common.io.stream.Writeable; +import org.elasticsearch.common.xcontent.ToXContent; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.index.fielddata.SortedNumericDoubleValues; import org.elasticsearch.search.aggregations.Aggregator; import org.elasticsearch.search.aggregations.AggregatorFactories; @@ -31,9 +39,11 @@ import org.elasticsearch.search.aggregations.bucket.BucketsAggregator; import org.elasticsearch.search.aggregations.pipeline.PipelineAggregator; import org.elasticsearch.search.aggregations.support.AggregationContext; +import org.elasticsearch.search.aggregations.support.ValueType; import org.elasticsearch.search.aggregations.support.ValuesSource; +import org.elasticsearch.search.aggregations.support.ValuesSource.Numeric; import org.elasticsearch.search.aggregations.support.ValuesSourceAggregatorFactory; -import org.elasticsearch.search.aggregations.support.ValuesSourceParser; +import org.elasticsearch.search.aggregations.support.ValuesSourceType; import org.elasticsearch.search.aggregations.support.format.ValueFormat; import org.elasticsearch.search.aggregations.support.format.ValueFormatter; import org.elasticsearch.search.aggregations.support.format.ValueParser; @@ -43,21 +53,38 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Objects; /** * */ public class RangeAggregator extends BucketsAggregator { - public static class Range { + public static final ParseField RANGES_FIELD = new ParseField("ranges"); + public static final ParseField KEYED_FIELD = new ParseField("keyed"); - public String key; - public double from = Double.NEGATIVE_INFINITY; - String fromAsStr; - public double to = Double.POSITIVE_INFINITY; - String toAsStr; + public static class Range implements Writeable, ToXContent { - public Range(String key, double from, String fromAsStr, double to, String toAsStr) { + public static final Range PROTOTYPE = new Range(null, -1, null, -1, null); + public static final ParseField KEY_FIELD = new ParseField("key"); + public static final ParseField FROM_FIELD = new ParseField("from"); + public static final ParseField TO_FIELD = new ParseField("to"); + + protected String key; + protected double from = Double.NEGATIVE_INFINITY; + protected String fromAsStr; + protected double to = Double.POSITIVE_INFINITY; + protected String toAsStr; + + public Range(String key, double from, double to) { + this(key, from, null, to, null); + } + + public Range(String key, String from, String to) { + this(key, Double.NEGATIVE_INFINITY, from, Double.POSITIVE_INFINITY, to); + } + + protected Range(String key, double from, String fromAsStr, double to, String toAsStr) { this.key = key; this.from = from; this.fromAsStr = fromAsStr; @@ -83,6 +110,99 @@ public void process(ValueParser parser, SearchContext context) { to = parser.parseDouble(toAsStr, context); } } + + @Override + public Range readFrom(StreamInput in) throws IOException { + String key = in.readOptionalString(); + String fromAsStr = in.readOptionalString(); + String toAsStr = in.readOptionalString(); + double from = in.readDouble(); + double to = in.readDouble(); + return new Range(key, from, fromAsStr, to, toAsStr); + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeOptionalString(key); + out.writeOptionalString(fromAsStr); + out.writeOptionalString(toAsStr); + out.writeDouble(from); + out.writeDouble(to); + } + + public Range fromXContent(XContentParser parser, ParseFieldMatcher parseFieldMatcher) throws IOException { + + XContentParser.Token token; + String currentFieldName = null; + double from = Double.NEGATIVE_INFINITY; + String fromAsStr = null; + double to = Double.POSITIVE_INFINITY; + String toAsStr = null; + String key = null; + while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { + if (token == XContentParser.Token.FIELD_NAME) { + currentFieldName = parser.currentName(); + } else if (token == XContentParser.Token.VALUE_NUMBER) { + if (parseFieldMatcher.match(currentFieldName, FROM_FIELD)) { + from = parser.doubleValue(); + } else if (parseFieldMatcher.match(currentFieldName, TO_FIELD)) { + to = parser.doubleValue(); + } + } else if (token == XContentParser.Token.VALUE_STRING) { + if (parseFieldMatcher.match(currentFieldName, FROM_FIELD)) { + fromAsStr = parser.text(); + } else if (parseFieldMatcher.match(currentFieldName, TO_FIELD)) { + toAsStr = parser.text(); + } else if (parseFieldMatcher.match(currentFieldName, KEY_FIELD)) { + key = parser.text(); + } + } + } + return new Range(key, from, fromAsStr, to, toAsStr); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(); + if (key != null) { + builder.field(KEY_FIELD.getPreferredName(), key); + } + if (Double.isFinite(from)) { + builder.field(FROM_FIELD.getPreferredName(), from); + } + if (Double.isFinite(to)) { + builder.field(TO_FIELD.getPreferredName(), to); + } + if (fromAsStr != null) { + builder.field(FROM_FIELD.getPreferredName(), fromAsStr); + } + if (toAsStr != null) { + builder.field(TO_FIELD.getPreferredName(), toAsStr); + } + builder.endObject(); + return builder; + } + + @Override + public int hashCode() { + return Objects.hash(key, from, fromAsStr, to, toAsStr); + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + Range other = (Range) obj; + return Objects.equals(key, other.key) + && Objects.equals(from, other.from) + && Objects.equals(fromAsStr, other.fromAsStr) + && Objects.equals(to, other.to) + && Objects.equals(toAsStr, other.toAsStr); + } } final ValuesSource.Numeric valuesSource; @@ -94,7 +214,7 @@ public void process(ValueParser parser, SearchContext context) { final double[] maxTo; public RangeAggregator(String name, AggregatorFactories factories, ValuesSource.Numeric valuesSource, ValueFormat format, - InternalRange.Factory rangeFactory, List ranges, boolean keyed, AggregationContext aggregationContext, + InternalRange.Factory rangeFactory, List ranges, boolean keyed, AggregationContext aggregationContext, Aggregator parent, List pipelineAggregators, Map metaData) throws IOException { super(name, factories, aggregationContext, parent, pipelineAggregators, metaData); @@ -245,12 +365,13 @@ protected int compare(int i, int j) { public static class Unmapped extends NonCollectingAggregator { - private final List ranges; + private final List ranges; private final boolean keyed; private final InternalRange.Factory factory; private final ValueFormatter formatter; - public Unmapped(String name, List ranges, boolean keyed, ValueFormat format, AggregationContext context, + public Unmapped(String name, List ranges, boolean keyed, ValueFormat format, + AggregationContext context, Aggregator parent, InternalRange.Factory factory, List pipelineAggregators, Map metaData) throws IOException { @@ -279,17 +400,27 @@ public InternalAggregation buildEmptyAggregation() { public static class Factory extends ValuesSourceAggregatorFactory { private final InternalRange.Factory rangeFactory; - private final List ranges; - private final boolean keyed; + private final List ranges; + private boolean keyed = false; + + public Factory(String name, List ranges) { + this(name, InternalRange.FACTORY, ranges); + } - public Factory(String name, ValuesSourceParser.Input valueSourceInput, InternalRange.Factory rangeFactory, - List ranges, boolean keyed) { - super(name, rangeFactory.type(), valueSourceInput); + protected Factory(String name, InternalRange.Factory rangeFactory, List ranges) { + super(name, rangeFactory.type(), rangeFactory.getValueSourceType(), rangeFactory.getValueType()); this.rangeFactory = rangeFactory; this.ranges = ranges; + } + + public void keyed(boolean keyed) { this.keyed = keyed; } + public boolean keyed() { + return keyed; + } + @Override protected Aggregator createUnmapped(AggregationContext aggregationContext, Aggregator parent, List pipelineAggregators, Map metaData) throws IOException { @@ -301,6 +432,51 @@ protected Aggregator doCreateInternal(ValuesSource.Numeric valuesSource, Aggrega boolean collectsFromSingleBucket, List pipelineAggregators, Map metaData) throws IOException { return new RangeAggregator(name, factories, valuesSource, config.format(), rangeFactory, ranges, keyed, aggregationContext, parent, pipelineAggregators, metaData); } + + @Override + protected XContentBuilder doXContentBody(XContentBuilder builder, Params params) throws IOException { + builder.field(RANGES_FIELD.getPreferredName(), ranges); + builder.field(KEYED_FIELD.getPreferredName(), keyed); + return builder; + } + + @Override + protected ValuesSourceAggregatorFactory innerReadFrom(String name, ValuesSourceType valuesSourceType, + ValueType targetValueType, StreamInput in) throws IOException { + Factory factory = createFactoryFromStream(name, in); + factory.keyed = in.readBoolean(); + return factory; + } + + protected Factory createFactoryFromStream(String name, StreamInput in) throws IOException { + int size = in.readVInt(); + List ranges = new ArrayList<>(size); + for (int i = 0; i < size; i++) { + ranges.add(Range.PROTOTYPE.readFrom(in)); + } + return new Factory(name, ranges); + } + + @Override + protected void innerWriteTo(StreamOutput out) throws IOException { + out.writeVInt(ranges.size()); + for (Range range : ranges) { + range.writeTo(out); + } + out.writeBoolean(keyed); + } + + @Override + protected int innerHashCode() { + return Objects.hash(ranges, keyed); + } + + @Override + protected boolean innerEquals(Object obj) { + Factory other = (Factory) obj; + return Objects.equals(ranges, other.ranges) + && Objects.equals(keyed, other.keyed); + } } } diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/bucket/range/RangeParser.java b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/range/RangeParser.java index 04b3c24cc8b95..d6f7d5b69c1c2 100644 --- a/core/src/main/java/org/elasticsearch/search/aggregations/bucket/range/RangeParser.java +++ b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/range/RangeParser.java @@ -18,22 +18,36 @@ */ package org.elasticsearch.search.aggregations.bucket.range; +import org.elasticsearch.common.ParseField; +import org.elasticsearch.common.ParseFieldMatcher; import org.elasticsearch.common.xcontent.XContentParser; -import org.elasticsearch.search.SearchParseException; -import org.elasticsearch.search.aggregations.Aggregator; +import org.elasticsearch.common.xcontent.XContentParser.Token; import org.elasticsearch.search.aggregations.AggregatorFactory; -import org.elasticsearch.search.aggregations.support.ValuesSource; -import org.elasticsearch.search.aggregations.support.ValuesSourceParser; -import org.elasticsearch.search.internal.SearchContext; +import org.elasticsearch.search.aggregations.bucket.range.RangeAggregator.Range; +import org.elasticsearch.search.aggregations.support.AbstractValuesSourceParser.NumericValuesSourceParser; +import org.elasticsearch.search.aggregations.support.ValueType; +import org.elasticsearch.search.aggregations.support.ValuesSource.Numeric; +import org.elasticsearch.search.aggregations.support.ValuesSourceAggregatorFactory; +import org.elasticsearch.search.aggregations.support.ValuesSourceType; import java.io.IOException; import java.util.ArrayList; +import java.util.Collections; import java.util.List; +import java.util.Map; /** * */ -public class RangeParser implements Aggregator.Parser { +public class RangeParser extends NumericValuesSourceParser { + + public RangeParser() { + this(true, true, false); + } + + protected RangeParser(boolean scriptable, boolean formattable, boolean timezoneAware) { + super(scriptable, formattable, timezoneAware); + } @Override public String type() { @@ -41,81 +55,46 @@ public String type() { } @Override - public AggregatorFactory parse(String aggregationName, XContentParser parser, SearchContext context) throws IOException { - - List ranges = null; - boolean keyed = false; - - ValuesSourceParser vsParser = ValuesSourceParser.numeric(aggregationName, InternalRange.TYPE, context) - .formattable(true) - .build(); + protected ValuesSourceAggregatorFactory createFactory(String aggregationName, ValuesSourceType valuesSourceType, + ValueType targetValueType, Map otherOptions) { + List ranges = (List) otherOptions.get(RangeAggregator.RANGES_FIELD); + RangeAggregator.Factory factory = new RangeAggregator.Factory(aggregationName, ranges); + Boolean keyed = (Boolean) otherOptions.get(RangeAggregator.KEYED_FIELD); + if (keyed != null) { + factory.keyed(keyed); + } + return factory; + } - XContentParser.Token token; - String currentFieldName = null; - while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { - if (token == XContentParser.Token.FIELD_NAME) { - currentFieldName = parser.currentName(); - } else if (vsParser.token(currentFieldName, token, parser)) { - continue; - } else if (token == XContentParser.Token.START_ARRAY) { - if ("ranges".equals(currentFieldName)) { - ranges = new ArrayList<>(); - while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) { - double from = Double.NEGATIVE_INFINITY; - String fromAsStr = null; - double to = Double.POSITIVE_INFINITY; - String toAsStr = null; - String key = null; - String toOrFromOrKey = null; - while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { - if (token == XContentParser.Token.FIELD_NAME) { - toOrFromOrKey = parser.currentName(); - } else if (token == XContentParser.Token.VALUE_NUMBER) { - if ("from".equals(toOrFromOrKey)) { - from = parser.doubleValue(); - } else if ("to".equals(toOrFromOrKey)) { - to = parser.doubleValue(); - } - } else if (token == XContentParser.Token.VALUE_STRING) { - if ("from".equals(toOrFromOrKey)) { - fromAsStr = parser.text(); - } else if ("to".equals(toOrFromOrKey)) { - toAsStr = parser.text(); - } else if ("key".equals(toOrFromOrKey)) { - key = parser.text(); - } - } - } - ranges.add(new RangeAggregator.Range(key, from, fromAsStr, to, toAsStr)); - } - } else { - throw new SearchParseException(context, "Unknown key for a " + token + " in [" + aggregationName + "]: [" - + currentFieldName + "].", parser.getTokenLocation()); - } - } else if (token == XContentParser.Token.VALUE_BOOLEAN) { - if ("keyed".equals(currentFieldName)) { - keyed = parser.booleanValue(); - } else { - throw new SearchParseException(context, "Unknown key for a " + token + " in [" + aggregationName + "]: [" - + currentFieldName + "].", parser.getTokenLocation()); + @Override + protected boolean token(String aggregationName, String currentFieldName, Token token, XContentParser parser, + ParseFieldMatcher parseFieldMatcher, Map otherOptions) throws IOException { + if (token == XContentParser.Token.START_ARRAY) { + if (parseFieldMatcher.match(currentFieldName, RangeAggregator.RANGES_FIELD)) { + List ranges = new ArrayList<>(); + while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) { + Range range = parseRange(parser, parseFieldMatcher); + ranges.add(range); } - } else { - throw new SearchParseException(context, "Unexpected token " + token + " in [" + aggregationName + "].", - parser.getTokenLocation()); + otherOptions.put(RangeAggregator.RANGES_FIELD, ranges); + return true; + } + } else if (token == XContentParser.Token.VALUE_BOOLEAN) { + if (parseFieldMatcher.match(currentFieldName, RangeAggregator.KEYED_FIELD)) { + boolean keyed = parser.booleanValue(); + otherOptions.put(RangeAggregator.KEYED_FIELD, keyed); + return true; } } + return false; + } - if (ranges == null) { - throw new SearchParseException(context, "Missing [ranges] in ranges aggregator [" + aggregationName + "]", - parser.getTokenLocation()); - } - - return new RangeAggregator.Factory(aggregationName, vsParser.input(), InternalRange.FACTORY, ranges, keyed); + protected Range parseRange(XContentParser parser, ParseFieldMatcher parseFieldMatcher) throws IOException { + return Range.PROTOTYPE.fromXContent(parser, parseFieldMatcher); } - // NORELEASE implement this method when refactoring this aggregation @Override public AggregatorFactory[] getFactoryPrototypes() { - return null; + return new AggregatorFactory[] { new RangeAggregator.Factory(null, Collections.emptyList()) }; } } diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/bucket/range/date/DateRangeAggregatorFactory.java b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/range/date/DateRangeAggregatorFactory.java new file mode 100644 index 0000000000000..a47bb13988e09 --- /dev/null +++ b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/range/date/DateRangeAggregatorFactory.java @@ -0,0 +1,51 @@ +/* + * 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.range.date; + +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.search.aggregations.bucket.range.RangeAggregator.Factory; +import org.elasticsearch.search.aggregations.bucket.range.RangeAggregator.Range; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +public class DateRangeAggregatorFactory extends Factory { + + public DateRangeAggregatorFactory(String name, List ranges) { + super(name, InternalDateRange.FACTORY, ranges); + } + + @Override + public String getWriteableName() { + return InternalDateRange.TYPE.name(); + } + + @Override + protected DateRangeAggregatorFactory createFactoryFromStream(String name, StreamInput in) throws IOException { + int size = in.readVInt(); + List ranges = new ArrayList<>(size); + for (int i = 0; i < size; i++) { + ranges.add(Range.PROTOTYPE.readFrom(in)); + } + return new DateRangeAggregatorFactory(name, ranges); + } + +} diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/bucket/range/date/DateRangeParser.java b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/range/date/DateRangeParser.java index b4269e7afa5f6..86494767b3637 100644 --- a/core/src/main/java/org/elasticsearch/search/aggregations/bucket/range/date/DateRangeParser.java +++ b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/range/date/DateRangeParser.java @@ -18,24 +18,28 @@ */ package org.elasticsearch.search.aggregations.bucket.range.date; -import org.elasticsearch.common.xcontent.XContentParser; -import org.elasticsearch.search.SearchParseException; -import org.elasticsearch.search.aggregations.Aggregator; +import org.elasticsearch.common.ParseField; import org.elasticsearch.search.aggregations.AggregatorFactory; import org.elasticsearch.search.aggregations.bucket.range.RangeAggregator; +import org.elasticsearch.search.aggregations.bucket.range.RangeAggregator.Range; +import org.elasticsearch.search.aggregations.bucket.range.RangeParser; import org.elasticsearch.search.aggregations.support.ValueType; -import org.elasticsearch.search.aggregations.support.ValuesSource; -import org.elasticsearch.search.aggregations.support.ValuesSourceParser; -import org.elasticsearch.search.internal.SearchContext; +import org.elasticsearch.search.aggregations.support.ValuesSource.Numeric; +import org.elasticsearch.search.aggregations.support.ValuesSourceAggregatorFactory; +import org.elasticsearch.search.aggregations.support.ValuesSourceType; -import java.io.IOException; -import java.util.ArrayList; +import java.util.Collections; import java.util.List; +import java.util.Map; /** * */ -public class DateRangeParser implements Aggregator.Parser { +public class DateRangeParser extends RangeParser { + + public DateRangeParser() { + super(true, true, true); + } @Override public String type() { @@ -43,84 +47,19 @@ public String type() { } @Override - public AggregatorFactory parse(String aggregationName, XContentParser parser, SearchContext context) throws IOException { - - ValuesSourceParser vsParser = ValuesSourceParser.numeric(aggregationName, InternalDateRange.TYPE, context) - .targetValueType(ValueType.DATE) - .formattable(true) - .build(); - - List ranges = null; - boolean keyed = false; - - XContentParser.Token token; - String currentFieldName = null; - while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { - if (token == XContentParser.Token.FIELD_NAME) { - currentFieldName = parser.currentName(); - } else if (vsParser.token(currentFieldName, token, parser)) { - continue; - } else if (token == XContentParser.Token.START_ARRAY) { - if ("ranges".equals(currentFieldName)) { - ranges = new ArrayList<>(); - while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) { - double from = Double.NEGATIVE_INFINITY; - String fromAsStr = null; - double to = Double.POSITIVE_INFINITY; - String toAsStr = null; - String key = null; - String toOrFromOrKey = null; - while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { - if (token == XContentParser.Token.FIELD_NAME) { - toOrFromOrKey = parser.currentName(); - } else if (token == XContentParser.Token.VALUE_NUMBER) { - if ("from".equals(toOrFromOrKey)) { - from = parser.doubleValue(); - } else if ("to".equals(toOrFromOrKey)) { - to = parser.doubleValue(); - } else { - throw new SearchParseException(context, "Unknown key for a " + token + " in [" + aggregationName - + "]: [" + currentFieldName + "].", parser.getTokenLocation()); - } - } else if (token == XContentParser.Token.VALUE_STRING) { - if ("from".equals(toOrFromOrKey)) { - fromAsStr = parser.text(); - } else if ("to".equals(toOrFromOrKey)) { - toAsStr = parser.text(); - } else if ("key".equals(toOrFromOrKey)) { - key = parser.text(); - } else { - throw new SearchParseException(context, "Unknown key for a " + token + " in [" + aggregationName + "]: [" + currentFieldName + "].", parser.getTokenLocation()); - } - } - } - ranges.add(new RangeAggregator.Range(key, from, fromAsStr, to, toAsStr)); - } - } - } else if (token == XContentParser.Token.VALUE_BOOLEAN) { - if ("keyed".equals(currentFieldName)) { - keyed = parser.booleanValue(); - } else { - throw new SearchParseException(context, "Unknown key for a " + token + " in [" + aggregationName + "]: [" - + currentFieldName + "].", parser.getTokenLocation()); - } - } else { - throw new SearchParseException(context, "Unexpected token " + token + " in [" + aggregationName + "].", - parser.getTokenLocation()); - } - } - - if (ranges == null) { - throw new SearchParseException(context, "Missing [ranges] in ranges aggregator [" + aggregationName + "]", - parser.getTokenLocation()); + protected ValuesSourceAggregatorFactory createFactory(String aggregationName, ValuesSourceType valuesSourceType, + ValueType targetValueType, Map otherOptions) { + List ranges = (List) otherOptions.get(RangeAggregator.RANGES_FIELD); + DateRangeAggregatorFactory factory = new DateRangeAggregatorFactory(aggregationName, ranges); + Boolean keyed = (Boolean) otherOptions.get(RangeAggregator.KEYED_FIELD); + if (keyed != null) { + factory.keyed(keyed); } - - return new RangeAggregator.Factory(aggregationName, vsParser.input(), InternalDateRange.FACTORY, ranges, keyed); + return factory; } - // NORELEASE implement this method when refactoring this aggregation @Override public AggregatorFactory[] getFactoryPrototypes() { - return null; + return new AggregatorFactory[] { new DateRangeAggregatorFactory(null, Collections.emptyList()) }; } } diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/bucket/range/date/InternalDateRange.java b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/range/date/InternalDateRange.java index 41e0ef897b9b9..88568bcd0052c 100644 --- a/core/src/main/java/org/elasticsearch/search/aggregations/bucket/range/date/InternalDateRange.java +++ b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/range/date/InternalDateRange.java @@ -26,6 +26,7 @@ import org.elasticsearch.search.aggregations.bucket.BucketStreams; import org.elasticsearch.search.aggregations.bucket.range.InternalRange; import org.elasticsearch.search.aggregations.pipeline.PipelineAggregator; +import org.elasticsearch.search.aggregations.support.ValueType; import org.elasticsearch.search.aggregations.support.format.ValueFormatter; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; @@ -119,6 +120,11 @@ public Type type() { return TYPE; } + @Override + public ValueType getValueType() { + return ValueType.DATE; + } + @Override public InternalDateRange create(String name, List ranges, ValueFormatter formatter, boolean keyed, List pipelineAggregators, Map metaData) { diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/bucket/range/geodistance/GeoDistanceParser.java b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/range/geodistance/GeoDistanceParser.java index b6d8e289fc4ab..3f24d7fd267e0 100644 --- a/core/src/main/java/org/elasticsearch/search/aggregations/bucket/range/geodistance/GeoDistanceParser.java +++ b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/range/geodistance/GeoDistanceParser.java @@ -21,170 +21,229 @@ import org.apache.lucene.index.LeafReaderContext; import org.apache.lucene.index.SortedNumericDocValues; import org.elasticsearch.common.ParseField; +import org.elasticsearch.common.ParseFieldMatcher; import org.elasticsearch.common.geo.GeoDistance; import org.elasticsearch.common.geo.GeoDistance.FixedSourceDistance; import org.elasticsearch.common.geo.GeoPoint; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.unit.DistanceUnit; +import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.common.xcontent.XContentParser.Token; import org.elasticsearch.index.fielddata.MultiGeoPointValues; import org.elasticsearch.index.fielddata.SortedBinaryDocValues; import org.elasticsearch.index.fielddata.SortedNumericDoubleValues; -import org.elasticsearch.search.SearchParseException; import org.elasticsearch.search.aggregations.Aggregator; import org.elasticsearch.search.aggregations.AggregatorFactory; import org.elasticsearch.search.aggregations.bucket.range.InternalRange; import org.elasticsearch.search.aggregations.bucket.range.RangeAggregator; import org.elasticsearch.search.aggregations.bucket.range.RangeAggregator.Unmapped; import org.elasticsearch.search.aggregations.pipeline.PipelineAggregator; +import org.elasticsearch.search.aggregations.support.AbstractValuesSourceParser.GeoPointValuesSourceParser; import org.elasticsearch.search.aggregations.support.AggregationContext; import org.elasticsearch.search.aggregations.support.GeoPointParser; +import org.elasticsearch.search.aggregations.support.ValueType; import org.elasticsearch.search.aggregations.support.ValuesSource; import org.elasticsearch.search.aggregations.support.ValuesSourceAggregatorFactory; -import org.elasticsearch.search.aggregations.support.ValuesSourceParser; -import org.elasticsearch.search.internal.SearchContext; +import org.elasticsearch.search.aggregations.support.ValuesSourceType; import java.io.IOException; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Objects; /** * */ -public class GeoDistanceParser implements Aggregator.Parser { +public class GeoDistanceParser extends GeoPointValuesSourceParser { private static final ParseField ORIGIN_FIELD = new ParseField("origin", "center", "point", "por"); + private static final ParseField UNIT_FIELD = new ParseField("unit"); + private static final ParseField DISTANCE_TYPE_FIELD = new ParseField("distance_type"); + + private GeoPointParser geoPointParser = new GeoPointParser(InternalGeoDistance.TYPE, ORIGIN_FIELD); + + public GeoDistanceParser() { + super(true, false); + } @Override public String type() { return InternalGeoDistance.TYPE.name(); } - private static String key(String key, double from, double to) { - if (key != null) { - return key; + public static class Range extends RangeAggregator.Range { + + static final Range PROTOTYPE = new Range(null, -1, -1); + + public Range(String key, double from, double to) { + super(key(key, from, to), from, to); } - StringBuilder sb = new StringBuilder(); - sb.append(from == 0 ? "*" : from); - sb.append("-"); - sb.append(Double.isInfinite(to) ? "*" : to); - return sb.toString(); + + private static String key(String key, double from, double to) { + if (key != null) { + return key; + } + StringBuilder sb = new StringBuilder(); + sb.append(from == 0 ? "*" : from); + sb.append("-"); + sb.append(Double.isInfinite(to) ? "*" : to); + return sb.toString(); + } + + @Override + public Range readFrom(StreamInput in) throws IOException { + String key = in.readOptionalString(); + double from = in.readDouble(); + double to = in.readDouble(); + return new Range(key, from, to); + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeOptionalString(key); + out.writeDouble(from); + out.writeDouble(to); + } + } @Override - public AggregatorFactory parse(String aggregationName, XContentParser parser, SearchContext context) throws IOException { - - ValuesSourceParser vsParser = ValuesSourceParser.geoPoint(aggregationName, InternalGeoDistance.TYPE, context).build(); - - GeoPointParser geoPointParser = new GeoPointParser(aggregationName, InternalGeoDistance.TYPE, context, ORIGIN_FIELD); - - List ranges = null; - DistanceUnit unit = DistanceUnit.DEFAULT; - GeoDistance distanceType = GeoDistance.DEFAULT; - boolean keyed = false; - - XContentParser.Token token; - String currentFieldName = null; - while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { - if (token == XContentParser.Token.FIELD_NAME) { - currentFieldName = parser.currentName(); - } else if (vsParser.token(currentFieldName, token, parser)) { - continue; - } else if (geoPointParser.token(currentFieldName, token, parser)) { - continue; - } else if (token == XContentParser.Token.VALUE_STRING) { - if ("unit".equals(currentFieldName)) { - unit = DistanceUnit.fromString(parser.text()); - } else if ("distance_type".equals(currentFieldName) || "distanceType".equals(currentFieldName)) { - distanceType = GeoDistance.fromString(parser.text()); - } else { - throw new SearchParseException(context, "Unknown key for a " + token + " in [" + aggregationName + "]: [" - + currentFieldName + "].", parser.getTokenLocation()); - } - } else if (token == XContentParser.Token.VALUE_BOOLEAN) { - if ("keyed".equals(currentFieldName)) { - keyed = parser.booleanValue(); - } else { - throw new SearchParseException(context, "Unknown key for a " + token + " in [" + aggregationName + "]: [" - + currentFieldName + "].", parser.getTokenLocation()); - } - } else if (token == XContentParser.Token.START_ARRAY) { - if ("ranges".equals(currentFieldName)) { - ranges = new ArrayList<>(); - while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) { - String fromAsStr = null; - String toAsStr = null; - double from = 0.0; - double to = Double.POSITIVE_INFINITY; - String key = null; - String toOrFromOrKey = null; - while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { - if (token == XContentParser.Token.FIELD_NAME) { - toOrFromOrKey = parser.currentName(); - } else if (token == XContentParser.Token.VALUE_NUMBER) { - if ("from".equals(toOrFromOrKey)) { - from = parser.doubleValue(); - } else if ("to".equals(toOrFromOrKey)) { - to = parser.doubleValue(); - } - } else if (token == XContentParser.Token.VALUE_STRING) { - if ("key".equals(toOrFromOrKey)) { - key = parser.text(); - } else if ("from".equals(toOrFromOrKey)) { - fromAsStr = parser.text(); - } else if ("to".equals(toOrFromOrKey)) { - toAsStr = parser.text(); - } + protected ValuesSourceAggregatorFactory createFactory( + String aggregationName, ValuesSourceType valuesSourceType, ValueType targetValueType, Map otherOptions) { + GeoPoint origin = (GeoPoint) otherOptions.get(ORIGIN_FIELD); + List ranges = (List) otherOptions.get(RangeAggregator.RANGES_FIELD); + GeoDistanceFactory factory = new GeoDistanceFactory(aggregationName, origin, ranges); + Boolean keyed = (Boolean) otherOptions.get(RangeAggregator.KEYED_FIELD); + if (keyed != null) { + factory.keyed(keyed); + } + DistanceUnit unit = (DistanceUnit) otherOptions.get(UNIT_FIELD); + if (unit != null) { + factory.unit(unit); + } + GeoDistance distanceType = (GeoDistance) otherOptions.get(DISTANCE_TYPE_FIELD); + if (distanceType != null) { + factory.distanceType(distanceType); + } + return factory; + } + + @Override + protected boolean token(String aggregationName, String currentFieldName, Token token, XContentParser parser, + ParseFieldMatcher parseFieldMatcher, Map otherOptions) throws IOException { + if (geoPointParser.token(aggregationName, currentFieldName, token, parser, parseFieldMatcher, otherOptions)) { + return true; + } else if (token == XContentParser.Token.VALUE_STRING) { + if (parseFieldMatcher.match(currentFieldName, UNIT_FIELD)) { + DistanceUnit unit = DistanceUnit.fromString(parser.text()); + otherOptions.put(UNIT_FIELD, unit); + return true; + } else if (parseFieldMatcher.match(currentFieldName, DISTANCE_TYPE_FIELD)) { + GeoDistance distanceType = GeoDistance.fromString(parser.text()); + otherOptions.put(DISTANCE_TYPE_FIELD, distanceType); + return true; + } + } else if (token == XContentParser.Token.VALUE_BOOLEAN) { + if (parseFieldMatcher.match(currentFieldName, RangeAggregator.KEYED_FIELD)) { + boolean keyed = parser.booleanValue(); + otherOptions.put(RangeAggregator.KEYED_FIELD, keyed); + return true; + } + } else if (token == XContentParser.Token.START_ARRAY) { + if (parseFieldMatcher.match(currentFieldName, RangeAggregator.RANGES_FIELD)) { + List ranges = new ArrayList<>(); + while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) { + String fromAsStr = null; + String toAsStr = null; + double from = 0.0; + double to = Double.POSITIVE_INFINITY; + String key = null; + String toOrFromOrKey = null; + while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { + if (token == XContentParser.Token.FIELD_NAME) { + toOrFromOrKey = parser.currentName(); + } else if (token == XContentParser.Token.VALUE_NUMBER) { + if (parseFieldMatcher.match(toOrFromOrKey, Range.FROM_FIELD)) { + from = parser.doubleValue(); + } else if (parseFieldMatcher.match(toOrFromOrKey, Range.TO_FIELD)) { + to = parser.doubleValue(); + } + } else if (token == XContentParser.Token.VALUE_STRING) { + if (parseFieldMatcher.match(toOrFromOrKey, Range.KEY_FIELD)) { + key = parser.text(); + } else if (parseFieldMatcher.match(toOrFromOrKey, Range.FROM_FIELD)) { + fromAsStr = parser.text(); + } else if (parseFieldMatcher.match(toOrFromOrKey, Range.TO_FIELD)) { + toAsStr = parser.text(); } } - ranges.add(new RangeAggregator.Range(key(key, from, to), from, fromAsStr, to, toAsStr)); } - } else { - throw new SearchParseException(context, "Unknown key for a " + token + " in [" + aggregationName + "]: [" - + currentFieldName + "].", parser.getTokenLocation()); + if (fromAsStr != null || toAsStr != null) { + ranges.add(new Range(key, Double.parseDouble(fromAsStr), Double.parseDouble(toAsStr))); + } else { + ranges.add(new Range(key, from, to)); + } } - } else { - throw new SearchParseException(context, "Unexpected token " + token + " in [" + aggregationName + "]: [" - + currentFieldName + "].", parser.getTokenLocation()); + otherOptions.put(RangeAggregator.RANGES_FIELD, ranges); + return true; } } - - if (ranges == null) { - throw new SearchParseException(context, "Missing [ranges] in geo_distance aggregator [" + aggregationName + "]", - parser.getTokenLocation()); - } - - GeoPoint origin = geoPointParser.geoPoint(); - if (origin == null) { - throw new SearchParseException(context, "Missing [origin] in geo_distance aggregator [" + aggregationName + "]", - parser.getTokenLocation()); - } - - return new GeoDistanceFactory(aggregationName, vsParser.input(), InternalGeoDistance.FACTORY, origin, unit, distanceType, ranges, - keyed); + return false; } - private static class GeoDistanceFactory extends ValuesSourceAggregatorFactory { + public static class GeoDistanceFactory extends ValuesSourceAggregatorFactory { private final GeoPoint origin; - private final DistanceUnit unit; - private final GeoDistance distanceType; private final InternalRange.Factory rangeFactory; - private final List ranges; - private final boolean keyed; + private final List ranges; + private DistanceUnit unit = DistanceUnit.DEFAULT; + private GeoDistance distanceType = GeoDistance.DEFAULT; + private boolean keyed = false; + + public GeoDistanceFactory(String name, GeoPoint origin, List ranges) { + this(name, origin, InternalGeoDistance.FACTORY, ranges); + } - public GeoDistanceFactory(String name, ValuesSourceParser.Input valueSourceInput, - InternalRange.Factory rangeFactory, GeoPoint origin, DistanceUnit unit, GeoDistance distanceType, - List ranges, boolean keyed) { - super(name, rangeFactory.type(), valueSourceInput); + private GeoDistanceFactory(String name, GeoPoint origin, InternalRange.Factory rangeFactory, List ranges) { + super(name, rangeFactory.type(), rangeFactory.getValueSourceType(), rangeFactory.getValueType()); this.origin = origin; - this.unit = unit; - this.distanceType = distanceType; this.rangeFactory = rangeFactory; this.ranges = ranges; + } + + @Override + public String getWriteableName() { + return InternalGeoDistance.TYPE.name(); + } + + public void unit(DistanceUnit unit) { + this.unit = unit; + } + + public DistanceUnit unit() { + return unit; + } + + public void distanceType(GeoDistance distanceType) { + this.distanceType = distanceType; + } + + public GeoDistance distanceType() { + return distanceType; + } + + public void keyed(boolean keyed) { this.keyed = keyed; } + public boolean keyed() { + return keyed; + } + @Override protected Aggregator createUnmapped(AggregationContext aggregationContext, Aggregator parent, List pipelineAggregators, Map metaData) throws IOException { @@ -203,6 +262,60 @@ protected Aggregator doCreateInternal(final ValuesSource.GeoPoint valuesSource, pipelineAggregators, metaData); } + @Override + protected XContentBuilder doXContentBody(XContentBuilder builder, Params params) throws IOException { + builder.field(ORIGIN_FIELD.getPreferredName(), origin); + builder.field(RangeAggregator.RANGES_FIELD.getPreferredName(), ranges); + builder.field(RangeAggregator.KEYED_FIELD.getPreferredName(), keyed); + builder.field(UNIT_FIELD.getPreferredName(), unit); + builder.field(DISTANCE_TYPE_FIELD.getPreferredName(), distanceType); + return builder; + } + + @Override + protected ValuesSourceAggregatorFactory innerReadFrom( + String name, ValuesSourceType valuesSourceType, ValueType targetValueType, StreamInput in) throws IOException { + GeoPoint origin = new GeoPoint(in.readDouble(), in.readDouble()); + int size = in.readVInt(); + List ranges = new ArrayList<>(size); + for (int i = 0; i < size; i++) { + ranges.add(Range.PROTOTYPE.readFrom(in)); + } + GeoDistanceFactory factory = new GeoDistanceFactory(name, origin, ranges); + factory.keyed = in.readBoolean(); + factory.distanceType = GeoDistance.readGeoDistanceFrom(in); + factory.unit = DistanceUnit.readDistanceUnit(in); + return factory; + } + + @Override + protected void innerWriteTo(StreamOutput out) throws IOException { + out.writeDouble(origin.lat()); + out.writeDouble(origin.lon()); + out.writeVInt(ranges.size()); + for (Range range : ranges) { + range.writeTo(out); + } + out.writeBoolean(keyed); + distanceType.writeTo(out); + DistanceUnit.writeDistanceUnit(out, unit); + } + + @Override + protected int innerHashCode() { + return Objects.hash(origin, ranges, keyed, distanceType, unit); + } + + @Override + protected boolean innerEquals(Object obj) { + GeoDistanceFactory other = (GeoDistanceFactory) obj; + return Objects.equals(origin, other.origin) + && Objects.equals(ranges, other.ranges) + && Objects.equals(keyed, other.keyed) + && Objects.equals(distanceType, other.distanceType) + && Objects.equals(unit, other.unit); + } + private static class DistanceSource extends ValuesSource.Numeric { private final ValuesSource.GeoPoint source; @@ -244,10 +357,9 @@ public SortedBinaryDocValues bytesValues(LeafReaderContext ctx) { } - // NORELEASE implement this method when refactoring this aggregation @Override public AggregatorFactory[] getFactoryPrototypes() { - return null; + return new AggregatorFactory[] { new GeoDistanceFactory(null, null, Collections.emptyList()) }; } } \ No newline at end of file diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/bucket/range/geodistance/InternalGeoDistance.java b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/range/geodistance/InternalGeoDistance.java index bedc6962bdc60..2c16d93ea02f0 100644 --- a/core/src/main/java/org/elasticsearch/search/aggregations/bucket/range/geodistance/InternalGeoDistance.java +++ b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/range/geodistance/InternalGeoDistance.java @@ -26,6 +26,8 @@ import org.elasticsearch.search.aggregations.bucket.BucketStreams; import org.elasticsearch.search.aggregations.bucket.range.InternalRange; import org.elasticsearch.search.aggregations.pipeline.PipelineAggregator; +import org.elasticsearch.search.aggregations.support.ValueType; +import org.elasticsearch.search.aggregations.support.ValuesSourceType; import org.elasticsearch.search.aggregations.support.format.ValueFormatter; import java.io.IOException; @@ -108,6 +110,16 @@ public Type type() { return TYPE; } + @Override + public ValuesSourceType getValueSourceType() { + return ValuesSourceType.GEOPOINT; + } + + @Override + public ValueType getValueType() { + return ValueType.GEOPOINT; + } + @Override public InternalGeoDistance create(String name, List ranges, ValueFormatter formatter, boolean keyed, List pipelineAggregators, Map metaData) { diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/bucket/range/ipv4/IPv4RangeAggregatorFactory.java b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/range/ipv4/IPv4RangeAggregatorFactory.java new file mode 100644 index 0000000000000..46ed00054dac7 --- /dev/null +++ b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/range/ipv4/IPv4RangeAggregatorFactory.java @@ -0,0 +1,197 @@ +/* + * 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.range.ipv4; + +import org.elasticsearch.common.ParseField; +import org.elasticsearch.common.ParseFieldMatcher; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.common.network.Cidrs; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.search.aggregations.bucket.range.RangeAggregator; +import org.elasticsearch.search.aggregations.bucket.range.RangeAggregator.Factory; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +public class IPv4RangeAggregatorFactory extends Factory { + + public IPv4RangeAggregatorFactory(String name, List ranges) { + super(name, InternalIPv4Range.FACTORY, ranges); + } + + @Override + public String getWriteableName() { + return InternalIPv4Range.TYPE.name(); + } + + @Override + protected IPv4RangeAggregatorFactory createFactoryFromStream(String name, StreamInput in) throws IOException { + int size = in.readVInt(); + List ranges = new ArrayList<>(size); + for (int i = 0; i < size; i++) { + ranges.add(Range.PROTOTYPE.readFrom(in)); + } + return new IPv4RangeAggregatorFactory(name, ranges); + } + + public static class Range extends RangeAggregator.Range { + + static final Range PROTOTYPE = new Range(null, -1, null, -1, null, null); + static final ParseField MASK_FIELD = new ParseField("mask"); + + private String cidr; + + public Range(String key, double from, double to) { + super(key, from, to); + } + + public Range(String key, String from, String to) { + super(key, from, to); + } + + public Range(String key, String cidr) { + super(key, -1, null, -1, null); + this.cidr = cidr; + if (cidr != null) { + parseMaskRange(); + } + } + + private Range(String key, double from, String fromAsStr, double to, String toAsStr, String cidr) { + super(key, from, fromAsStr, to, toAsStr); + this.cidr = cidr; + if (cidr != null) { + parseMaskRange(); + } + } + + public String mask() { + return cidr; + } + + private void parseMaskRange() throws IllegalArgumentException { + long[] fromTo = Cidrs.cidrMaskToMinMax(cidr); + from = fromTo[0] == 0 ? Double.NEGATIVE_INFINITY : fromTo[0]; + to = fromTo[1] == InternalIPv4Range.MAX_IP ? Double.POSITIVE_INFINITY : fromTo[1]; + if (key == null) { + key = cidr; + } + } + + @Override + public Range fromXContent(XContentParser parser, ParseFieldMatcher parseFieldMatcher) throws IOException { + + XContentParser.Token token; + String currentFieldName = null; + double from = Double.NEGATIVE_INFINITY; + String fromAsStr = null; + double to = Double.POSITIVE_INFINITY; + String toAsStr = null; + String key = null; + String cidr = null; + while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { + if (token == XContentParser.Token.FIELD_NAME) { + currentFieldName = parser.currentName(); + } else if (token == XContentParser.Token.VALUE_NUMBER) { + if (parseFieldMatcher.match(currentFieldName, FROM_FIELD)) { + from = parser.doubleValue(); + } else if (parseFieldMatcher.match(currentFieldName, TO_FIELD)) { + to = parser.doubleValue(); + } + } else if (token == XContentParser.Token.VALUE_STRING) { + if (parseFieldMatcher.match(currentFieldName, FROM_FIELD)) { + fromAsStr = parser.text(); + } else if (parseFieldMatcher.match(currentFieldName, TO_FIELD)) { + toAsStr = parser.text(); + } else if (parseFieldMatcher.match(currentFieldName, KEY_FIELD)) { + key = parser.text(); + } else if (parseFieldMatcher.match(currentFieldName, MASK_FIELD)) { + cidr = parser.text(); + } + } + } + return new Range(key, from, fromAsStr, to, toAsStr, cidr); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(); + if (key != null) { + builder.field(KEY_FIELD.getPreferredName(), key); + } + if (cidr != null) { + builder.field(MASK_FIELD.getPreferredName(), cidr); + } else { + if (Double.isFinite(from)) { + builder.field(FROM_FIELD.getPreferredName(), from); + } + if (Double.isFinite(to)) { + builder.field(TO_FIELD.getPreferredName(), to); + } + if (fromAsStr != null) { + builder.field(FROM_FIELD.getPreferredName(), fromAsStr); + } + if (toAsStr != null) { + builder.field(TO_FIELD.getPreferredName(), toAsStr); + } + } + builder.endObject(); + return builder; + } + + @Override + public Range readFrom(StreamInput in) throws IOException { + String key = in.readOptionalString(); + String fromAsStr = in.readOptionalString(); + String toAsStr = in.readOptionalString(); + double from = in.readDouble(); + double to = in.readDouble(); + String mask = in.readOptionalString(); + return new Range(key, from, fromAsStr, to, toAsStr, mask); + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeOptionalString(key); + out.writeOptionalString(fromAsStr); + out.writeOptionalString(toAsStr); + out.writeDouble(from); + out.writeDouble(to); + out.writeOptionalString(cidr); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), cidr); + } + + @Override + public boolean equals(Object obj) { + return super.equals(obj) + && Objects.equals(cidr, ((Range) obj).cidr); + } + + } + +} diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/bucket/range/ipv4/InternalIPv4Range.java b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/range/ipv4/InternalIPv4Range.java index 1589b1621aeb4..a6c3ed38b1eb0 100644 --- a/core/src/main/java/org/elasticsearch/search/aggregations/bucket/range/ipv4/InternalIPv4Range.java +++ b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/range/ipv4/InternalIPv4Range.java @@ -26,14 +26,13 @@ import org.elasticsearch.search.aggregations.bucket.BucketStreams; import org.elasticsearch.search.aggregations.bucket.range.InternalRange; import org.elasticsearch.search.aggregations.pipeline.PipelineAggregator; +import org.elasticsearch.search.aggregations.support.ValueType; import org.elasticsearch.search.aggregations.support.format.ValueFormatter; import java.io.IOException; import java.util.List; import java.util.Map; -import static org.elasticsearch.index.mapper.ip.IpFieldMapper.MAX_IP; - /** * */ @@ -117,6 +116,11 @@ public Type type() { return TYPE; } + @Override + public ValueType getValueType() { + return ValueType.IP; + } + @Override public InternalIPv4Range create(String name, List ranges, ValueFormatter formatter, boolean keyed, List pipelineAggregators, Map metaData) { diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/bucket/range/ipv4/IpRangeParser.java b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/range/ipv4/IpRangeParser.java index ff6f3563a8132..eaa7a5fd3f817 100644 --- a/core/src/main/java/org/elasticsearch/search/aggregations/bucket/range/ipv4/IpRangeParser.java +++ b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/range/ipv4/IpRangeParser.java @@ -18,26 +18,31 @@ */ package org.elasticsearch.search.aggregations.bucket.range.ipv4; -import org.elasticsearch.common.network.Cidrs; +import org.elasticsearch.common.ParseField; +import org.elasticsearch.common.ParseFieldMatcher; import org.elasticsearch.common.xcontent.XContentParser; -import org.elasticsearch.index.mapper.ip.IpFieldMapper; -import org.elasticsearch.search.SearchParseException; -import org.elasticsearch.search.aggregations.Aggregator; import org.elasticsearch.search.aggregations.AggregatorFactory; import org.elasticsearch.search.aggregations.bucket.range.RangeAggregator; +import org.elasticsearch.search.aggregations.bucket.range.RangeAggregator.Range; +import org.elasticsearch.search.aggregations.bucket.range.RangeParser; import org.elasticsearch.search.aggregations.support.ValueType; -import org.elasticsearch.search.aggregations.support.ValuesSource; -import org.elasticsearch.search.aggregations.support.ValuesSourceParser; -import org.elasticsearch.search.internal.SearchContext; +import org.elasticsearch.search.aggregations.support.ValuesSource.Numeric; +import org.elasticsearch.search.aggregations.support.ValuesSourceAggregatorFactory; +import org.elasticsearch.search.aggregations.support.ValuesSourceType; import java.io.IOException; -import java.util.ArrayList; +import java.util.Collections; import java.util.List; +import java.util.Map; /** * */ -public class IpRangeParser implements Aggregator.Parser { +public class IpRangeParser extends RangeParser { + + public IpRangeParser() { + super(true, false, false); + } @Override public String type() { @@ -45,105 +50,26 @@ public String type() { } @Override - public AggregatorFactory parse(String aggregationName, XContentParser parser, SearchContext context) throws IOException { - - ValuesSourceParser vsParser = ValuesSourceParser.numeric(aggregationName, InternalIPv4Range.TYPE, context) - .targetValueType(ValueType.IP) - .formattable(false) - .build(); - - List ranges = null; - boolean keyed = false; - - XContentParser.Token token; - String currentFieldName = null; - while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { - if (token == XContentParser.Token.FIELD_NAME) { - currentFieldName = parser.currentName(); - } else if (vsParser.token(currentFieldName, token, parser)) { - continue; - } else if (token == XContentParser.Token.START_ARRAY) { - if ("ranges".equals(currentFieldName)) { - ranges = new ArrayList<>(); - while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) { - double from = Double.NEGATIVE_INFINITY; - String fromAsStr = null; - double to = Double.POSITIVE_INFINITY; - String toAsStr = null; - String key = null; - String mask = null; - String toOrFromOrMaskOrKey = null; - while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { - if (token == XContentParser.Token.FIELD_NAME) { - toOrFromOrMaskOrKey = parser.currentName(); - } else if (token == XContentParser.Token.VALUE_NUMBER) { - if ("from".equals(toOrFromOrMaskOrKey)) { - from = parser.doubleValue(); - } else if ("to".equals(toOrFromOrMaskOrKey)) { - to = parser.doubleValue(); - } - } else if (token == XContentParser.Token.VALUE_STRING) { - if ("from".equals(toOrFromOrMaskOrKey)) { - fromAsStr = parser.text(); - } else if ("to".equals(toOrFromOrMaskOrKey)) { - toAsStr = parser.text(); - } else if ("key".equals(toOrFromOrMaskOrKey)) { - key = parser.text(); - } else if ("mask".equals(toOrFromOrMaskOrKey)) { - mask = parser.text(); - } - } - } - RangeAggregator.Range range = new RangeAggregator.Range(key, from, fromAsStr, to, toAsStr); - if (mask != null) { - parseMaskRange(mask, range, aggregationName, context); - } - ranges.add(range); - } - } else { - throw new SearchParseException(context, "Unknown key for a " + token + " in [" + aggregationName + "]: [" - + currentFieldName + "].", parser.getTokenLocation()); - } - } else if (token == XContentParser.Token.VALUE_BOOLEAN) { - if ("keyed".equals(currentFieldName)) { - keyed = parser.booleanValue(); - } else { - throw new SearchParseException(context, "Unknown key for a " + token + " in [" + aggregationName + "]: [" - + currentFieldName + "].", parser.getTokenLocation()); - } - } else { - throw new SearchParseException(context, "Unexpected token " + token + " in [" + aggregationName + "].", - parser.getTokenLocation()); - } - } - - if (ranges == null) { - throw new SearchParseException(context, "Missing [ranges] in ranges aggregator [" + aggregationName + "]", - parser.getTokenLocation()); - } - - return new RangeAggregator.Factory(aggregationName, vsParser.input(), InternalIPv4Range.FACTORY, ranges, keyed); + protected Range parseRange(XContentParser parser, ParseFieldMatcher parseFieldMatcher) throws IOException { + return IPv4RangeAggregatorFactory.Range.PROTOTYPE.fromXContent(parser, parseFieldMatcher); } - private static void parseMaskRange(String cidr, RangeAggregator.Range range, String aggregationName, SearchContext ctx) { - long[] fromTo; - try { - fromTo = Cidrs.cidrMaskToMinMax(cidr); - } catch (IllegalArgumentException e) { - throw new SearchParseException(ctx, "invalid CIDR mask [" + cidr + "] in aggregation [" + aggregationName + "]", - null, e); - } - range.from = fromTo[0] == 0 ? Double.NEGATIVE_INFINITY : fromTo[0]; - range.to = fromTo[1] == InternalIPv4Range.MAX_IP ? Double.POSITIVE_INFINITY : fromTo[1]; - if (range.key == null) { - range.key = cidr; + @Override + protected ValuesSourceAggregatorFactory createFactory(String aggregationName, ValuesSourceType valuesSourceType, + ValueType targetValueType, Map otherOptions) { + List ranges = (List) otherOptions + .get(RangeAggregator.RANGES_FIELD); + IPv4RangeAggregatorFactory factory = new IPv4RangeAggregatorFactory(aggregationName, ranges); + Boolean keyed = (Boolean) otherOptions.get(RangeAggregator.KEYED_FIELD); + if (keyed != null) { + factory.keyed(keyed); } + return factory; } - // NORELEASE implement this method when refactoring this aggregation @Override public AggregatorFactory[] getFactoryPrototypes() { - return null; + return new AggregatorFactory[] { new IPv4RangeAggregatorFactory(null, Collections.emptyList()) }; } } diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/support/GeoPointParser.java b/core/src/main/java/org/elasticsearch/search/aggregations/support/GeoPointParser.java index 3dfab20074716..fd2f3636d173f 100644 --- a/core/src/main/java/org/elasticsearch/search/aggregations/support/GeoPointParser.java +++ b/core/src/main/java/org/elasticsearch/search/aggregations/support/GeoPointParser.java @@ -19,41 +19,39 @@ package org.elasticsearch.search.aggregations.support; + import org.elasticsearch.common.ParseField; +import org.elasticsearch.common.ParseFieldMatcher; +import org.elasticsearch.common.ParsingException; import org.elasticsearch.common.geo.GeoPoint; import org.elasticsearch.common.xcontent.XContentParser; -import org.elasticsearch.search.SearchParseException; import org.elasticsearch.search.aggregations.InternalAggregation; -import org.elasticsearch.search.internal.SearchContext; import java.io.IOException; +import java.util.Map; /** * */ public class GeoPointParser { - private final String aggName; private final InternalAggregation.Type aggType; - private final SearchContext context; private final ParseField field; - GeoPoint point; - - public GeoPointParser(String aggName, InternalAggregation.Type aggType, SearchContext context, ParseField field) { - this.aggName = aggName; + public GeoPointParser(InternalAggregation.Type aggType, ParseField field) { this.aggType = aggType; - this.context = context; this.field = field; } - public boolean token(String currentFieldName, XContentParser.Token token, XContentParser parser) throws IOException { - if (!context.parseFieldMatcher().match(currentFieldName, field)) { + public boolean token(String aggName, String currentFieldName, XContentParser.Token token, XContentParser parser, + ParseFieldMatcher parseFieldMatcher, Map otherOptions) throws IOException { + if (!parseFieldMatcher.match(currentFieldName, field)) { return false; } if (token == XContentParser.Token.VALUE_STRING) { - point = new GeoPoint(); + GeoPoint point = new GeoPoint(); point.resetFromString(parser.text()); + otherOptions.put(field, point); return true; } if (token == XContentParser.Token.START_ARRAY) { @@ -65,12 +63,12 @@ public boolean token(String currentFieldName, XContentParser.Token token, XConte } else if (Double.isNaN(lat)) { lat = parser.doubleValue(); } else { - throw new SearchParseException(context, "malformed [" + currentFieldName + "] geo point array in [" + - aggName + "] " + aggType + " aggregation. a geo point array must be of the form [lon, lat]", - parser.getTokenLocation()); + throw new ParsingException(parser.getTokenLocation(), "malformed [" + currentFieldName + "] geo point array in [" + + aggName + "] " + aggType + " aggregation. a geo point array must be of the form [lon, lat]"); } } - point = new GeoPoint(lat, lon); + GeoPoint point = new GeoPoint(lat, lon); + otherOptions.put(field, point); return true; } if (token == XContentParser.Token.START_OBJECT) { @@ -88,17 +86,15 @@ public boolean token(String currentFieldName, XContentParser.Token token, XConte } } if (Double.isNaN(lat) || Double.isNaN(lon)) { - throw new SearchParseException(context, "malformed [" + currentFieldName + "] geo point object. either [lat] or [lon] (or both) are " + - "missing in [" + aggName + "] " + aggType + " aggregation", parser.getTokenLocation()); + throw new ParsingException(parser.getTokenLocation(), + "malformed [" + currentFieldName + "] geo point object. either [lat] or [lon] (or both) are " + "missing in [" + + aggName + "] " + aggType + " aggregation"); } - point = new GeoPoint(lat, lon); + GeoPoint point = new GeoPoint(lat, lon); + otherOptions.put(field, point); return true; } return false; } - public GeoPoint geoPoint() { - return point; - } - } diff --git a/core/src/test/java/org/elasticsearch/search/aggregations/bucket/DateRangeTests.java b/core/src/test/java/org/elasticsearch/search/aggregations/bucket/DateRangeTests.java new file mode 100644 index 0000000000000..ed3696da267c9 --- /dev/null +++ b/core/src/test/java/org/elasticsearch/search/aggregations/bucket/DateRangeTests.java @@ -0,0 +1,66 @@ +/* + * 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; + +import org.elasticsearch.search.aggregations.BaseAggregationTestCase; +import org.elasticsearch.search.aggregations.bucket.range.RangeAggregator.Range; +import org.elasticsearch.search.aggregations.bucket.range.date.DateRangeAggregatorFactory; + +import java.util.ArrayList; +import java.util.List; + +public class DateRangeTests extends BaseAggregationTestCase { + + @Override + protected DateRangeAggregatorFactory createTestAggregatorFactory() { + int numRanges = randomIntBetween(1, 10); + List ranges = new ArrayList<>(numRanges); + for (int i = 0; i < numRanges; i++) { + String key = null; + if (randomBoolean()) { + key = randomAsciiOfLengthBetween(1, 20); + } + double from = randomBoolean() ? Double.NEGATIVE_INFINITY : randomIntBetween(Integer.MIN_VALUE, Integer.MAX_VALUE - 1000); + double to = randomBoolean() ? Double.POSITIVE_INFINITY + : (Double.isInfinite(from) ? randomIntBetween(Integer.MIN_VALUE, Integer.MAX_VALUE) + : randomIntBetween((int) from, Integer.MAX_VALUE)); + if (randomBoolean()) { + ranges.add(new Range(key, from, to)); + } else { + String fromAsStr = Double.isInfinite(from) ? null : String.valueOf(from); + String toAsStr = Double.isInfinite(to) ? null : String.valueOf(to); + ranges.add(new Range(key, fromAsStr, toAsStr)); + } + } + DateRangeAggregatorFactory factory = new DateRangeAggregatorFactory("foo", ranges); + factory.field(INT_FIELD_NAME); + if (randomBoolean()) { + factory.format("###.##"); + } + if (randomBoolean()) { + factory.keyed(randomBoolean()); + } + if (randomBoolean()) { + factory.missing(randomIntBetween(0, 10)); + } + return factory; + } + +} diff --git a/core/src/test/java/org/elasticsearch/search/aggregations/bucket/GeoDistanceRangeTests.java b/core/src/test/java/org/elasticsearch/search/aggregations/bucket/GeoDistanceRangeTests.java new file mode 100644 index 0000000000000..9d579ad0425d2 --- /dev/null +++ b/core/src/test/java/org/elasticsearch/search/aggregations/bucket/GeoDistanceRangeTests.java @@ -0,0 +1,68 @@ +/* + * 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; + +import org.elasticsearch.common.geo.GeoDistance; +import org.elasticsearch.common.geo.GeoPoint; +import org.elasticsearch.common.unit.DistanceUnit; +import org.elasticsearch.search.aggregations.BaseAggregationTestCase; +import org.elasticsearch.search.aggregations.bucket.range.geodistance.GeoDistanceParser.GeoDistanceFactory; +import org.elasticsearch.search.aggregations.bucket.range.geodistance.GeoDistanceParser.Range; +import org.elasticsearch.test.geo.RandomShapeGenerator; + +import java.util.ArrayList; +import java.util.List; + +public class GeoDistanceRangeTests extends BaseAggregationTestCase { + + @Override + protected GeoDistanceFactory createTestAggregatorFactory() { + int numRanges = randomIntBetween(1, 10); + List ranges = new ArrayList<>(numRanges); + for (int i = 0; i < numRanges; i++) { + String key = null; + if (randomBoolean()) { + key = randomAsciiOfLengthBetween(1, 20); + } + double from = randomBoolean() ? 0 : randomIntBetween(0, Integer.MAX_VALUE - 1000); + double to = randomBoolean() ? Double.POSITIVE_INFINITY + : (Double.compare(from, 0) == 0 ? randomIntBetween(0, Integer.MAX_VALUE) + : randomIntBetween((int) from, Integer.MAX_VALUE)); + ranges.add(new Range(key, from, to)); + } + GeoPoint origin = RandomShapeGenerator.randomPoint(getRandom()); + GeoDistanceFactory factory = new GeoDistanceFactory("foo", origin, ranges); + factory.field(randomAsciiOfLengthBetween(1, 20)); + if (randomBoolean()) { + factory.keyed(randomBoolean()); + } + if (randomBoolean()) { + factory.missing("0, 0"); + } + if (randomBoolean()) { + factory.unit(randomFrom(DistanceUnit.values())); + } + if (randomBoolean()) { + factory.distanceType(randomFrom(GeoDistance.values())); + } + return factory; + } + +} diff --git a/core/src/test/java/org/elasticsearch/search/aggregations/bucket/IPv4RangeTests.java b/core/src/test/java/org/elasticsearch/search/aggregations/bucket/IPv4RangeTests.java new file mode 100644 index 0000000000000..6457d0b26f21a --- /dev/null +++ b/core/src/test/java/org/elasticsearch/search/aggregations/bucket/IPv4RangeTests.java @@ -0,0 +1,75 @@ +/* + * 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; + +import org.elasticsearch.common.network.Cidrs; +import org.elasticsearch.index.mapper.ip.IpFieldMapper; +import org.elasticsearch.search.aggregations.BaseAggregationTestCase; +import org.elasticsearch.search.aggregations.bucket.range.ipv4.IPv4RangeAggregatorFactory; +import org.elasticsearch.search.aggregations.bucket.range.ipv4.IPv4RangeAggregatorFactory.Range; + +import java.util.ArrayList; +import java.util.List; + +public class IPv4RangeTests extends BaseAggregationTestCase { + + @Override + protected IPv4RangeAggregatorFactory createTestAggregatorFactory() { + int numRanges = randomIntBetween(1, 10); + List ranges = new ArrayList<>(numRanges); + for (int i = 0; i < numRanges; i++) { + String key = null; + if (randomBoolean()) { + key = randomAsciiOfLengthBetween(1, 20); + } + if (randomBoolean()) { + double from = randomBoolean() ? Double.NEGATIVE_INFINITY : randomIntBetween(Integer.MIN_VALUE, Integer.MAX_VALUE - 1000); + double to = randomBoolean() ? Double.POSITIVE_INFINITY + : (Double.isInfinite(from) ? randomIntBetween(Integer.MIN_VALUE, Integer.MAX_VALUE) + : randomIntBetween((int) from, Integer.MAX_VALUE)); + if (randomBoolean()) { + ranges.add(new Range(key, from, to)); + } else { + String fromAsStr = Double.isInfinite(from) ? null : IpFieldMapper.longToIp((long) from); + String toAsStr = Double.isInfinite(to) ? null : IpFieldMapper.longToIp((long) to); + ranges.add(new Range(key, fromAsStr, toAsStr)); + } + } else { + int mask = randomInt(32); + long ipAsLong = randomIntBetween(0, Integer.MAX_VALUE); + + long blockSize = 1L << (32 - mask); + ipAsLong = ipAsLong - (ipAsLong & (blockSize - 1)); + String cidr = Cidrs.createCIDR(ipAsLong, mask); + ranges.add(new Range(key, cidr)); + } + } + IPv4RangeAggregatorFactory factory = new IPv4RangeAggregatorFactory("foo", ranges); + factory.field(INT_FIELD_NAME); + if (randomBoolean()) { + factory.keyed(randomBoolean()); + } + if (randomBoolean()) { + factory.missing(randomIntBetween(0, 10)); + } + return factory; + } + +} diff --git a/core/src/test/java/org/elasticsearch/search/aggregations/bucket/RangeTests.java b/core/src/test/java/org/elasticsearch/search/aggregations/bucket/RangeTests.java new file mode 100644 index 0000000000000..9271e89dd1a75 --- /dev/null +++ b/core/src/test/java/org/elasticsearch/search/aggregations/bucket/RangeTests.java @@ -0,0 +1,67 @@ +/* + * 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; + +import org.elasticsearch.search.aggregations.BaseAggregationTestCase; +import org.elasticsearch.search.aggregations.bucket.range.RangeAggregator; +import org.elasticsearch.search.aggregations.bucket.range.RangeAggregator.Factory; +import org.elasticsearch.search.aggregations.bucket.range.RangeAggregator.Range; + +import java.util.ArrayList; +import java.util.List; + +public class RangeTests extends BaseAggregationTestCase { + + @Override + protected Factory createTestAggregatorFactory() { + int numRanges = randomIntBetween(1, 10); + List ranges = new ArrayList<>(numRanges); + for (int i = 0; i < numRanges; i++) { + String key = null; + if (randomBoolean()) { + key = randomAsciiOfLengthBetween(1, 20); + } + double from = randomBoolean() ? Double.NEGATIVE_INFINITY : randomIntBetween(Integer.MIN_VALUE, Integer.MAX_VALUE - 1000); + double to = randomBoolean() ? Double.POSITIVE_INFINITY + : (Double.isInfinite(from) ? randomIntBetween(Integer.MIN_VALUE, Integer.MAX_VALUE) + : randomIntBetween((int) from, Integer.MAX_VALUE)); + if (randomBoolean()) { + ranges.add(new Range(key, from, to)); + } else { + String fromAsStr = Double.isInfinite(from) ? null : String.valueOf(from); + String toAsStr = Double.isInfinite(to) ? null : String.valueOf(to); + ranges.add(new Range(key, fromAsStr, toAsStr)); + } + } + Factory factory = new Factory("foo", ranges); + factory.field(INT_FIELD_NAME); + if (randomBoolean()) { + factory.format("###.##"); + } + if (randomBoolean()) { + factory.keyed(randomBoolean()); + } + if (randomBoolean()) { + factory.missing(randomIntBetween(0, 10)); + } + return factory; + } + +}