diff --git a/core/src/main/java/org/elasticsearch/index/mapper/ip/LegacyIpFieldMapper.java b/core/src/main/java/org/elasticsearch/index/mapper/ip/LegacyIpFieldMapper.java index aef6869ffc3..01fa2106684 100644 --- a/core/src/main/java/org/elasticsearch/index/mapper/ip/LegacyIpFieldMapper.java +++ b/core/src/main/java/org/elasticsearch/index/mapper/ip/LegacyIpFieldMapper.java @@ -50,7 +50,6 @@ import org.elasticsearch.index.mapper.core.LegacyLongFieldMapper.CustomLongNumer import org.elasticsearch.index.mapper.core.LegacyNumberFieldMapper; import org.elasticsearch.index.query.QueryShardContext; import org.elasticsearch.search.DocValueFormat; -import org.elasticsearch.search.aggregations.bucket.range.ipv4.InternalIPv4Range; import org.joda.time.DateTimeZone; import java.io.IOException; @@ -218,7 +217,7 @@ public class LegacyIpFieldMapper extends LegacyNumberFieldMapper { } if (fromTo != null) { return rangeQuery(fromTo[0] == 0 ? null : fromTo[0], - fromTo[1] == InternalIPv4Range.MAX_IP ? null : fromTo[1], true, false); + fromTo[1] == MAX_IP ? null : fromTo[1], true, false); } } return super.termQuery(value, context); diff --git a/core/src/main/java/org/elasticsearch/search/SearchModule.java b/core/src/main/java/org/elasticsearch/search/SearchModule.java index 6fe4913d074..513a5c88a37 100644 --- a/core/src/main/java/org/elasticsearch/search/SearchModule.java +++ b/core/src/main/java/org/elasticsearch/search/SearchModule.java @@ -131,9 +131,9 @@ import org.elasticsearch.search.aggregations.bucket.range.date.InternalDateRange import org.elasticsearch.search.aggregations.bucket.range.geodistance.GeoDistanceAggregatorBuilder; import org.elasticsearch.search.aggregations.bucket.range.geodistance.GeoDistanceParser; import org.elasticsearch.search.aggregations.bucket.range.geodistance.InternalGeoDistance; -import org.elasticsearch.search.aggregations.bucket.range.ipv4.IPv4RangeAggregatorBuilder; -import org.elasticsearch.search.aggregations.bucket.range.ipv4.InternalIPv4Range; -import org.elasticsearch.search.aggregations.bucket.range.ipv4.IpRangeParser; +import org.elasticsearch.search.aggregations.bucket.range.ip.IpRangeAggregatorBuilder; +import org.elasticsearch.search.aggregations.bucket.range.InternalBinaryRange; +import org.elasticsearch.search.aggregations.bucket.range.ip.IpRangeParser; import org.elasticsearch.search.aggregations.bucket.sampler.DiversifiedAggregatorBuilder; import org.elasticsearch.search.aggregations.bucket.sampler.DiversifiedSamplerParser; import org.elasticsearch.search.aggregations.bucket.sampler.InternalSampler; @@ -507,7 +507,7 @@ public class SearchModule extends AbstractModule { SignificantTermsAggregatorBuilder.AGGREGATION_NAME_FIELD); registerAggregation(RangeAggregatorBuilder::new, new RangeParser(), RangeAggregatorBuilder.AGGREGATION_NAME_FIELD); registerAggregation(DateRangeAggregatorBuilder::new, new DateRangeParser(), DateRangeAggregatorBuilder.AGGREGATION_NAME_FIELD); - registerAggregation(IPv4RangeAggregatorBuilder::new, new IpRangeParser(), IPv4RangeAggregatorBuilder.AGGREGATION_NAME_FIELD); + registerAggregation(IpRangeAggregatorBuilder::new, new IpRangeParser(), IpRangeAggregatorBuilder.AGGREGATION_NAME_FIELD); registerAggregation(HistogramAggregatorBuilder::new, new HistogramParser(), HistogramAggregatorBuilder.AGGREGATION_NAME_FIELD); registerAggregation(DateHistogramAggregatorBuilder::new, new DateHistogramParser(), DateHistogramAggregatorBuilder.AGGREGATION_NAME_FIELD); @@ -736,7 +736,7 @@ public class SearchModule extends AbstractModule { UnmappedTerms.registerStreams(); InternalRange.registerStream(); InternalDateRange.registerStream(); - InternalIPv4Range.registerStream(); + InternalBinaryRange.registerStream(); InternalHistogram.registerStream(); InternalGeoDistance.registerStream(); InternalNested.registerStream(); diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/AggregationBuilders.java b/core/src/main/java/org/elasticsearch/search/aggregations/AggregationBuilders.java index 23fa8007d5b..b76d5d17891 100644 --- a/core/src/main/java/org/elasticsearch/search/aggregations/AggregationBuilders.java +++ b/core/src/main/java/org/elasticsearch/search/aggregations/AggregationBuilders.java @@ -45,7 +45,7 @@ import org.elasticsearch.search.aggregations.bucket.range.Range; import org.elasticsearch.search.aggregations.bucket.range.RangeAggregatorBuilder; import org.elasticsearch.search.aggregations.bucket.range.date.DateRangeAggregatorBuilder; import org.elasticsearch.search.aggregations.bucket.range.geodistance.GeoDistanceAggregatorBuilder; -import org.elasticsearch.search.aggregations.bucket.range.ipv4.IPv4RangeAggregatorBuilder; +import org.elasticsearch.search.aggregations.bucket.range.ip.IpRangeAggregatorBuilder; import org.elasticsearch.search.aggregations.bucket.sampler.DiversifiedAggregatorBuilder; import org.elasticsearch.search.aggregations.bucket.sampler.Sampler; import org.elasticsearch.search.aggregations.bucket.sampler.SamplerAggregatorBuilder; @@ -261,11 +261,11 @@ public class AggregationBuilders { } /** - * Create a new {@link IPv4RangeAggregatorBuilder} aggregation with the + * Create a new {@link IpRangeAggregatorBuilder} aggregation with the * given name. */ - public static IPv4RangeAggregatorBuilder ipRange(String name) { - return new IPv4RangeAggregatorBuilder(name); + public static IpRangeAggregatorBuilder ipRange(String name) { + return new IpRangeAggregatorBuilder(name); } /** diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/bucket/range/BinaryRangeAggregator.java b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/range/BinaryRangeAggregator.java new file mode 100644 index 00000000000..87a3e917ddd --- /dev/null +++ b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/range/BinaryRangeAggregator.java @@ -0,0 +1,234 @@ +/* + * 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; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; +import java.util.Map; + +import org.apache.lucene.index.LeafReaderContext; +import org.apache.lucene.index.SortedSetDocValues; +import org.apache.lucene.util.BytesRef; +import org.elasticsearch.search.DocValueFormat; +import org.elasticsearch.search.aggregations.Aggregator; +import org.elasticsearch.search.aggregations.AggregatorFactories; +import org.elasticsearch.search.aggregations.InternalAggregation; +import org.elasticsearch.search.aggregations.LeafBucketCollector; +import org.elasticsearch.search.aggregations.LeafBucketCollectorBase; +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.ValuesSource; + +/** A range aggregator for values that are stored in SORTED_SET doc values. */ +public final class BinaryRangeAggregator extends BucketsAggregator { + + public static class Range { + + final String key; + final BytesRef from, to; + + public Range(String key, BytesRef from, BytesRef to) { + this.key = key; + this.from = from; + this.to = to; + } + } + + static final Comparator RANGE_COMPARATOR = (a, b) -> { + int cmp = compare(a.from, b.from, 1); + if (cmp == 0) { + cmp = compare(a.to, b.to, -1); + } + return cmp; + }; + + private static int compare(BytesRef a, BytesRef b, int m) { + return a == null + ? b == null ? 0 : -m + : b == null ? m : a.compareTo(b); + } + + final ValuesSource.Bytes valuesSource; + final DocValueFormat format; + final boolean keyed; + final Range[] ranges; + + public BinaryRangeAggregator(String name, AggregatorFactories factories, + ValuesSource.Bytes valuesSource, DocValueFormat format, + List ranges, boolean keyed, AggregationContext aggregationContext, + Aggregator parent, List pipelineAggregators, + Map metaData) throws IOException { + super(name, factories, aggregationContext, parent, pipelineAggregators, metaData); + this.valuesSource = valuesSource; + this.format = format; + this.keyed = keyed; + this.ranges = ranges.toArray(new Range[0]); + Arrays.sort(this.ranges, RANGE_COMPARATOR); + + } + + @Override + public boolean needsScores() { + return (valuesSource != null && valuesSource.needsScores()) || super.needsScores(); + } + + @Override + protected LeafBucketCollector getLeafCollector(LeafReaderContext ctx, LeafBucketCollector sub) throws IOException { + if (valuesSource == null) { + return LeafBucketCollector.NO_OP_COLLECTOR; + } + if (valuesSource instanceof ValuesSource.Bytes.WithOrdinals) { + SortedSetDocValues values = ((ValuesSource.Bytes.WithOrdinals) valuesSource).ordinalsValues(ctx); + return new SortedSetRangeLeafCollector(values, ranges, sub) { + @Override + protected void doCollect(LeafBucketCollector sub, int doc, long bucket) throws IOException { + collectBucket(sub, doc, bucket); + } + }; + } + throw new IllegalArgumentException("binary range aggregation expects a values source that supports ordinals"); + } + + static abstract class SortedSetRangeLeafCollector extends LeafBucketCollectorBase { + + final long[] froms, tos, maxTos; + final SortedSetDocValues values; + final LeafBucketCollector sub; + + SortedSetRangeLeafCollector(SortedSetDocValues values, + Range[] ranges, LeafBucketCollector sub) { + super(sub, values); + for (int i = 1; i < ranges.length; ++i) { + if (RANGE_COMPARATOR.compare(ranges[i-1], ranges[i]) > 0) { + throw new IllegalArgumentException("Ranges must be sorted"); + } + } + this.values = values; + this.sub = sub; + froms = new long[ranges.length]; + tos = new long[ranges.length]; // inclusive + maxTos = new long[ranges.length]; + for (int i = 0; i < ranges.length; ++i) { + if (ranges[i].from == null) { + froms[i] = 0; + } else { + froms[i] = values.lookupTerm(ranges[i].from); + if (froms[i] < 0) { + froms[i] = -1 - froms[i]; + } + } + if (ranges[i].to == null) { + tos[i] = values.getValueCount() - 1; + } else { + long ord = values.lookupTerm(ranges[i].to); + if (ord < 0) { + tos[i] = -2 - ord; + } else { + tos[i] = ord - 1; + } + } + } + maxTos[0] = tos[0]; + for (int i = 1; i < tos.length; ++i) { + maxTos[i] = Math.max(maxTos[i-1], tos[i]); + } + } + + @Override + public void collect(int doc, long bucket) throws IOException { + values.setDocument(doc); + int lo = 0; + for (long ord = values.nextOrd(); ord != SortedSetDocValues.NO_MORE_ORDS; ord = values.nextOrd()) { + lo = collect(doc, ord, bucket, lo); + } + } + + private int collect(int doc, long ord, long bucket, int lowBound) throws IOException { + int lo = lowBound, hi = froms.length - 1; // all candidates are between these indexes + int mid = (lo + hi) >>> 1; + while (lo <= hi) { + if (ord < froms[mid]) { + hi = mid - 1; + } else if (ord > maxTos[mid]) { + lo = mid + 1; + } else { + break; + } + mid = (lo + hi) >>> 1; + } + if (lo > hi) return lo; // no potential candidate + + // binary search the lower bound + int startLo = lo, startHi = mid; + while (startLo <= startHi) { + final int startMid = (startLo + startHi) >>> 1; + if (ord > maxTos[startMid]) { + startLo = startMid + 1; + } else { + startHi = startMid - 1; + } + } + + // binary search the upper bound + int endLo = mid, endHi = hi; + while (endLo <= endHi) { + final int endMid = (endLo + endHi) >>> 1; + if (ord < froms[endMid]) { + endHi = endMid - 1; + } else { + endLo = endMid + 1; + } + } + + assert startLo == lowBound || ord > maxTos[startLo - 1]; + assert endHi == froms.length - 1 || ord < froms[endHi + 1]; + + for (int i = startLo; i <= endHi; ++i) { + if (ord <= tos[i]) { + doCollect(sub, doc, bucket * froms.length + i); + } + } + + return endHi + 1; + } + + protected abstract void doCollect(LeafBucketCollector sub, int doc, long bucket) throws IOException; + } + + @Override + public InternalAggregation buildAggregation(long bucket) throws IOException { + InternalBinaryRange.Bucket[] buckets = new InternalBinaryRange.Bucket[ranges.length]; + for (int i = 0; i < buckets.length; ++i) { + long bucketOrd = bucket * ranges.length + i; + buckets[i] = new InternalBinaryRange.Bucket(format, keyed, + ranges[i].key, ranges[i].from, ranges[i].to, + bucketDocCount(bucketOrd), bucketAggregations(bucketOrd)); + } + return new InternalBinaryRange(name, format, keyed, buckets, pipelineAggregators(), metaData()); + } + + @Override + public InternalAggregation buildEmptyAggregation() { + return new InternalBinaryRange(name, format, keyed, new InternalBinaryRange.Bucket[0], pipelineAggregators(), metaData()); + } + +} diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/bucket/range/BinaryRangeAggregatorFactory.java b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/range/BinaryRangeAggregatorFactory.java new file mode 100644 index 00000000000..fda822cf11b --- /dev/null +++ b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/range/BinaryRangeAggregatorFactory.java @@ -0,0 +1,70 @@ +/* + * 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; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +import org.elasticsearch.search.aggregations.Aggregator; +import org.elasticsearch.search.aggregations.AggregatorFactories.Builder; +import org.elasticsearch.search.aggregations.AggregatorFactory; +import org.elasticsearch.search.aggregations.InternalAggregation.Type; +import org.elasticsearch.search.aggregations.pipeline.PipelineAggregator; +import org.elasticsearch.search.aggregations.support.AggregationContext; +import org.elasticsearch.search.aggregations.support.ValuesSource; +import org.elasticsearch.search.aggregations.support.ValuesSourceAggregatorFactory; +import org.elasticsearch.search.aggregations.support.ValuesSourceConfig; + +public class BinaryRangeAggregatorFactory + extends ValuesSourceAggregatorFactory { + + private final List ranges; + private final boolean keyed; + + public BinaryRangeAggregatorFactory(String name, Type type, + ValuesSourceConfig config, + List ranges, boolean keyed, + AggregationContext context, + AggregatorFactory parent, Builder subFactoriesBuilder, + Map metaData) throws IOException { + super(name, type, config, context, parent, subFactoriesBuilder, metaData); + this.ranges = ranges; + this.keyed = keyed; + } + + @Override + protected Aggregator createUnmapped(Aggregator parent, + List pipelineAggregators, + Map metaData) throws IOException { + return new BinaryRangeAggregator(name, factories, null, config.format(), + ranges, keyed, context, parent, pipelineAggregators, metaData); + } + + @Override + protected Aggregator doCreateInternal(ValuesSource.Bytes valuesSource, + Aggregator parent, + boolean collectsFromSingleBucket, + List pipelineAggregators, + Map metaData) throws IOException { + return new BinaryRangeAggregator(name, factories, valuesSource, config.format(), + ranges, keyed, context, parent, pipelineAggregators, metaData); + } + +} diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/bucket/range/InternalBinaryRange.java b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/range/InternalBinaryRange.java new file mode 100644 index 00000000000..f3f1763113e --- /dev/null +++ b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/range/InternalBinaryRange.java @@ -0,0 +1,311 @@ +/* + * 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; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import org.apache.lucene.util.BytesRef; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.search.DocValueFormat; +import org.elasticsearch.search.aggregations.AggregationStreams; +import org.elasticsearch.search.aggregations.Aggregations; +import org.elasticsearch.search.aggregations.InternalAggregation; +import org.elasticsearch.search.aggregations.InternalAggregations; +import org.elasticsearch.search.aggregations.InternalMultiBucketAggregation; +import org.elasticsearch.search.aggregations.bucket.BucketStreamContext; +import org.elasticsearch.search.aggregations.bucket.BucketStreams; +import org.elasticsearch.search.aggregations.pipeline.PipelineAggregator; + +/** A range aggregation for data that is encoded in doc values using a binary representation. */ +public final class InternalBinaryRange + extends InternalMultiBucketAggregation + implements Range { + + public static final Type TYPE = new Type("binary_range"); + + private final static AggregationStreams.Stream STREAM = new AggregationStreams.Stream() { + @Override + public InternalBinaryRange readResult(StreamInput in) throws IOException { + InternalBinaryRange range = new InternalBinaryRange(); + range.readFrom(in); + return range; + } + }; + + private final static BucketStreams.Stream BUCKET_STREAM = new BucketStreams.Stream() { + @Override + public Bucket readResult(StreamInput in, BucketStreamContext context) throws IOException { + Bucket bucket = new Bucket(context.format(), context.keyed()); + bucket.readFrom(in); + return bucket; + } + + @Override + public BucketStreamContext getBucketStreamContext(Bucket bucket) { + BucketStreamContext context = new BucketStreamContext(); + context.format(bucket.format); + context.keyed(bucket.keyed); + return context; + } + }; + + public static void registerStream() { + AggregationStreams.registerStream(STREAM, TYPE.stream()); + BucketStreams.registerStream(BUCKET_STREAM, TYPE.stream()); + } + + public static class Bucket extends InternalMultiBucketAggregation.InternalBucket implements Range.Bucket { + + private final transient DocValueFormat format; + private final transient boolean keyed; + private String key; + private BytesRef from, to; + private long docCount; + private InternalAggregations aggregations; + + public Bucket(DocValueFormat format, boolean keyed, String key, BytesRef from, BytesRef to, + long docCount, InternalAggregations aggregations) { + this(format, keyed); + this.key = key; + this.from = from; + this.to = to; + this.docCount = docCount; + this.aggregations = aggregations; + } + + // for serialization + private Bucket(DocValueFormat format, boolean keyed) { + this.format = format; + this.keyed = keyed; + } + + @Override + public Object getKey() { + return key; + } + + @Override + public String getKeyAsString() { + return key; + } + + @Override + public long getDocCount() { + return docCount; + } + + @Override + public Aggregations getAggregations() { + return aggregations; + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + String key = this.key; + if (keyed) { + if (key == null) { + StringBuilder keyBuilder = new StringBuilder(); + keyBuilder.append(from == null ? "*" : format.format(from)); + keyBuilder.append("-"); + keyBuilder.append(to == null ? "*" : format.format(to)); + key = keyBuilder.toString(); + } + builder.startObject(key); + } else { + builder.startObject(); + if (key != null) { + builder.field(CommonFields.KEY, key); + } + } + if (from != null) { + builder.field(CommonFields.FROM, getFrom()); + } + if (to != null) { + builder.field(CommonFields.TO, getTo()); + } + builder.field(CommonFields.DOC_COUNT, docCount); + aggregations.toXContentInternal(builder, params); + builder.endObject(); + return builder; + } + + @Override + public void readFrom(StreamInput in) throws IOException { + key = in.readOptionalString(); + if (in.readBoolean()) { + from = in.readBytesRef(); + } else { + from = null; + } + if (in.readBoolean()) { + to = in.readBytesRef(); + } else { + to = null; + } + docCount = in.readLong(); + aggregations = InternalAggregations.readAggregations(in); + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeOptionalString(key); + out.writeBoolean(from != null); + if (from != null) { + out.writeBytesRef(from); + } + out.writeBoolean(to != null); + if (to != null) { + out.writeBytesRef(to); + } + out.writeLong(docCount); + aggregations.writeTo(out); + } + + @Override + public Object getFrom() { + return getFromAsString(); + } + + @Override + public String getFromAsString() { + return from == null ? null : format.format(from); + } + + @Override + public Object getTo() { + return getToAsString(); + } + + @Override + public String getToAsString() { + return to == null ? null : format.format(to); + } + + } + + private DocValueFormat format; + private boolean keyed; + private Bucket[] buckets; + + public InternalBinaryRange(String name, DocValueFormat format, boolean keyed, Bucket[] buckets, + List pipelineAggregators, Map metaData) { + super(name, pipelineAggregators, metaData); + this.format = format; + this.keyed = keyed; + this.buckets = buckets; + } + + private InternalBinaryRange() {} // for serialization + + @Override + public List getBuckets() { + return Arrays.asList(buckets); + } + + @Override + public InternalBinaryRange create(List buckets) { + return new InternalBinaryRange(name, format, keyed, buckets.toArray(new Bucket[0]), + pipelineAggregators(), metaData); + } + + @Override + public Bucket createBucket(InternalAggregations aggregations, Bucket prototype) { + return new Bucket(format, keyed, prototype.key, prototype.from, prototype.to, prototype.docCount, aggregations); + } + + @Override + public Type type() { + return TYPE; + } + + @Override + public InternalAggregation doReduce(List aggregations, ReduceContext reduceContext) { + long[] docCounts = new long[buckets.length]; + InternalAggregations[][] aggs = new InternalAggregations[buckets.length][]; + for (int i = 0; i < aggs.length; ++i) { + aggs[i] = new InternalAggregations[aggregations.size()]; + } + for (int i = 0; i < aggregations.size(); ++i) { + InternalBinaryRange range = (InternalBinaryRange) aggregations.get(i); + if (range.buckets.length != buckets.length) { + throw new IllegalStateException("Expected " + buckets.length + " buckets, but got " + range.buckets.length); + } + for (int j = 0; j < buckets.length; ++j) { + Bucket bucket = range.buckets[j]; + docCounts[j] += bucket.docCount; + aggs[j][i] = bucket.aggregations; + } + } + Bucket[] buckets = new Bucket[this.buckets.length]; + for (int i = 0; i < buckets.length; ++i) { + Bucket b = this.buckets[i]; + buckets[i] = new Bucket(format, keyed, b.key, b.from, b.to, docCounts[i], + InternalAggregations.reduce(Arrays.asList(aggs[i]), reduceContext)); + } + return new InternalBinaryRange(name, format, keyed, buckets, pipelineAggregators(), metaData); + } + + @Override + public XContentBuilder doXContentBody(XContentBuilder builder, + Params params) throws IOException { + if (keyed) { + builder.startObject(CommonFields.BUCKETS); + } else { + builder.startArray(CommonFields.BUCKETS); + } + for (Bucket range : buckets) { + range.toXContent(builder, params); + } + if (keyed) { + builder.endObject(); + } else { + builder.endArray(); + } + return builder; + } + + @Override + protected void doWriteTo(StreamOutput out) throws IOException { + out.writeNamedWriteable(format); + out.writeBoolean(keyed); + out.writeVInt(buckets.length); + for (Bucket bucket : buckets) { + bucket.writeTo(out); + } + } + + @Override + protected void doReadFrom(StreamInput in) throws IOException { + format = in.readNamedWriteable(DocValueFormat.class); + keyed = in.readBoolean(); + Bucket[] buckets = new Bucket[in.readVInt()]; + for (int i = 0; i < buckets.length; ++i) { + Bucket bucket = new Bucket(format, keyed); + bucket.readFrom(in); + buckets[i] = bucket; + } + this.buckets = buckets; + } + +} diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/bucket/range/ip/IpRangeAggregatorBuilder.java b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/range/ip/IpRangeAggregatorBuilder.java new file mode 100644 index 00000000000..c56a2952f8d --- /dev/null +++ b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/range/ip/IpRangeAggregatorBuilder.java @@ -0,0 +1,330 @@ +/* + * 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.ip; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +import org.apache.lucene.document.InetAddressPoint; +import org.apache.lucene.util.BytesRef; +import org.elasticsearch.common.ParseField; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.common.network.InetAddresses; +import org.elasticsearch.common.xcontent.ToXContent; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.script.Script; +import org.elasticsearch.search.aggregations.AggregatorFactories.Builder; +import org.elasticsearch.search.aggregations.AggregatorFactory; +import org.elasticsearch.search.aggregations.InternalAggregation; +import org.elasticsearch.search.aggregations.bucket.range.BinaryRangeAggregator; +import org.elasticsearch.search.aggregations.bucket.range.BinaryRangeAggregatorFactory; +import org.elasticsearch.search.aggregations.bucket.range.RangeAggregator; +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.ValuesSourceAggregatorBuilder; +import org.elasticsearch.search.aggregations.support.ValuesSourceAggregatorFactory; +import org.elasticsearch.search.aggregations.support.ValuesSourceConfig; +import org.elasticsearch.search.aggregations.support.ValuesSourceType; + + +public final class IpRangeAggregatorBuilder + extends ValuesSourceAggregatorBuilder { + private static final String NAME = "ip_range"; + public static final ParseField AGGREGATION_NAME_FIELD = new ParseField(NAME); + private static final InternalAggregation.Type TYPE = new InternalAggregation.Type(NAME); + + public static class Range implements ToXContent { + private final String key; + private final String from; + private final String to; + + Range(String key, String from, String to) { + if (from != null) { + InetAddresses.forString(from); + } + if (to != null) { + InetAddresses.forString(to); + } + this.key = key; + this.from = from; + this.to = to; + } + + Range(String key, String mask) { + String[] splits = mask.split("/"); + if (splits.length != 2) { + throw new IllegalArgumentException("Expected [ip/prefix_length] but got [" + mask + + "], which contains zero or more than one [/]"); + } + InetAddress value = InetAddresses.forString(splits[0]); + int prefixLength = Integer.parseInt(splits[1]); + // copied from InetAddressPoint.newPrefixQuery + if (prefixLength < 0 || prefixLength > 8 * value.getAddress().length) { + throw new IllegalArgumentException("illegal prefixLength [" + prefixLength + + "] in [" + mask + "]. Must be 0-32 for IPv4 ranges, 0-128 for IPv6 ranges"); + } + // create the lower value by zeroing out the host portion, upper value by filling it with all ones. + byte lower[] = value.getAddress(); + byte upper[] = value.getAddress(); + for (int i = prefixLength; i < 8 * lower.length; i++) { + int m = 1 << (7 - (i & 7)); + lower[i >> 3] &= ~m; + upper[i >> 3] |= m; + } + this.key = key; + try { + this.from = InetAddresses.toAddrString(InetAddress.getByAddress(lower)); + this.to = InetAddresses.toAddrString(InetAddress.getByAddress(upper)); + } catch (UnknownHostException bogus) { + throw new AssertionError(bogus); + } + } + + private Range(StreamInput in) throws IOException { + this.key = in.readOptionalString(); + this.from = in.readOptionalString(); + this.to = in.readOptionalString(); + } + + void writeTo(StreamOutput out) throws IOException { + out.writeOptionalString(key); + out.writeOptionalString(from); + out.writeOptionalString(to); + } + + public String getKey() { + return key; + } + + public String getFrom() { + return from; + } + + public String getTo() { + return to; + } + + @Override + public boolean equals(Object obj) { + if (obj == null || getClass() != obj.getClass()) { + return false; + } + Range that = (Range) obj; + return Objects.equals(key, that.key) + && Objects.equals(from, that.from) + && Objects.equals(to, that.to); + } + + @Override + public int hashCode() { + return Objects.hash(getClass(), key, from, to); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(); + if (key != null) { + builder.field(RangeAggregator.Range.KEY_FIELD.getPreferredName(), key); + } + if (from != null) { + builder.field(RangeAggregator.Range.FROM_FIELD.getPreferredName(), from); + } + if (to != null) { + builder.field(RangeAggregator.Range.TO_FIELD.getPreferredName(), to); + } + builder.endObject(); + return builder; + } + } + + private boolean keyed = false; + private List ranges = new ArrayList<>(); + + public IpRangeAggregatorBuilder(String name) { + super(name, TYPE, ValuesSourceType.BYTES, ValueType.IP); + } + + @Override + public String getWriteableName() { + return NAME; + } + + public IpRangeAggregatorBuilder keyed(boolean keyed) { + this.keyed = keyed; + return this; + } + + public boolean keyed() { + return keyed; + } + + /** Get the current list or ranges that are configured on this aggregation. */ + public List getRanges() { + return Collections.unmodifiableList(ranges); + } + + /** Add a new {@link Range} to this aggregation. */ + public IpRangeAggregatorBuilder addRange(Range range) { + ranges.add(range); + return this; + } + + /** + * Add a new range to this aggregation. + * + * @param key + * the key to use for this range in the response + * @param from + * the lower bound on the distances, inclusive + * @param to + * the upper bound on the distances, exclusive + */ + public IpRangeAggregatorBuilder addRange(String key, String from, String to) { + addRange(new Range(key, from, to)); + return this; + } + + /** + * Add a new range to this aggregation using the CIDR notation. + */ + public IpRangeAggregatorBuilder addMaskRange(String key, String mask) { + return addRange(new Range(key, mask)); + } + + /** + * Same as {@link #addMaskRange(String, String)} but uses the mask itself as + * a key. + */ + public IpRangeAggregatorBuilder addMaskRange(String mask) { + return addRange(new Range(mask, mask)); + } + + /** + * Same as {@link #addRange(String, String, String)} but the key will be + * automatically generated. + */ + public IpRangeAggregatorBuilder addRange(String from, String to) { + return addRange(null, from, to); + } + + /** + * Same as {@link #addRange(String, String, String)} but there will be no + * lower bound. + */ + public IpRangeAggregatorBuilder addUnboundedTo(String key, String to) { + addRange(new Range(key, null, to)); + return this; + } + + /** + * Same as {@link #addUnboundedTo(String, String)} but the key will be + * generated automatically. + */ + public IpRangeAggregatorBuilder addUnboundedTo(String to) { + return addUnboundedTo(null, to); + } + + /** + * Same as {@link #addRange(String, String, String)} but there will be no + * upper bound. + */ + public IpRangeAggregatorBuilder addUnboundedFrom(String key, String from) { + addRange(new Range(key, from, null)); + return this; + } + + @Override + public IpRangeAggregatorBuilder script(Script script) { + throw new IllegalArgumentException("[ip_range] does not support scripts"); + } + + /** + * Same as {@link #addUnboundedFrom(String, String)} but the key will be + * generated automatically. + */ + public IpRangeAggregatorBuilder addUnboundedFrom(String from) { + return addUnboundedFrom(null, from); + } + + public IpRangeAggregatorBuilder(StreamInput in) throws IOException { + super(in, TYPE, ValuesSourceType.BYTES, ValueType.IP); + final int numRanges = in.readVInt(); + for (int i = 0; i < numRanges; ++i) { + addRange(new Range(in)); + } + keyed = in.readBoolean(); + } + + @Override + protected void innerWriteTo(StreamOutput out) throws IOException { + out.writeVInt(ranges.size()); + for (Range range : ranges) { + range.writeTo(out); + } + out.writeBoolean(keyed); + } + + private static BytesRef toBytesRef(String ip) { + if (ip == null) { + return null; + } + InetAddress address = InetAddresses.forString(ip); + byte[] bytes = InetAddressPoint.encode(address); + return new BytesRef(bytes); + } + + @Override + protected ValuesSourceAggregatorFactory innerBuild( + AggregationContext context, ValuesSourceConfig config, + AggregatorFactory parent, Builder subFactoriesBuilder) + throws IOException { + List ranges = new ArrayList<>(); + for (Range range : this.ranges) { + ranges.add(new BinaryRangeAggregator.Range(range.key, toBytesRef(range.from), toBytesRef(range.to))); + } + return new BinaryRangeAggregatorFactory(name, TYPE, config, ranges, + keyed, context, parent, subFactoriesBuilder, metaData); + } + + @Override + protected XContentBuilder doXContentBody(XContentBuilder builder, Params params) throws IOException { + builder.field(RangeAggregator.RANGES_FIELD.getPreferredName(), ranges); + builder.field(RangeAggregator.KEYED_FIELD.getPreferredName(), keyed); + return builder; + } + + @Override + protected int innerHashCode() { + return Objects.hash(keyed, ranges); + } + + @Override + protected boolean innerEquals(Object obj) { + IpRangeAggregatorBuilder that = (IpRangeAggregatorBuilder) obj; + return keyed == that.keyed + && ranges.equals(that.ranges); + } +} diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/bucket/range/ip/IpRangeParser.java b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/range/ip/IpRangeParser.java new file mode 100644 index 00000000000..64ed77d42f3 --- /dev/null +++ b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/range/ip/IpRangeParser.java @@ -0,0 +1,126 @@ +/* + * 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.ip; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.elasticsearch.common.ParseField; +import org.elasticsearch.common.ParseFieldMatcher; +import org.elasticsearch.common.ParsingException; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.common.xcontent.XContentParser.Token; +import org.elasticsearch.search.aggregations.support.AbstractValuesSourceParser.BytesValuesSourceParser; +import org.elasticsearch.search.aggregations.bucket.range.RangeAggregator; +import org.elasticsearch.search.aggregations.bucket.range.ip.IpRangeAggregatorBuilder.Range; +import org.elasticsearch.search.aggregations.support.ValueType; +import org.elasticsearch.search.aggregations.support.ValuesSource; +import org.elasticsearch.search.aggregations.support.ValuesSourceAggregatorBuilder; +import org.elasticsearch.search.aggregations.support.ValuesSourceType; + +/** + * A parser for ip range aggregations. + */ +public class IpRangeParser extends BytesValuesSourceParser { + + private static final ParseField MASK_FIELD = new ParseField("mask"); + + public IpRangeParser() { + super(false, false); + } + + @Override + protected ValuesSourceAggregatorBuilder createFactory( + String aggregationName, ValuesSourceType valuesSourceType, + ValueType targetValueType, Map otherOptions) { + IpRangeAggregatorBuilder range = new IpRangeAggregatorBuilder(aggregationName); + @SuppressWarnings("unchecked") + Iterable ranges = (Iterable) otherOptions.get(RangeAggregator.RANGES_FIELD); + if (otherOptions.containsKey(RangeAggregator.RANGES_FIELD)) { + for (Range r : ranges) { + range.addRange(r); + } + } + if (otherOptions.containsKey(RangeAggregator.KEYED_FIELD)) { + range.keyed((Boolean) otherOptions.get(RangeAggregator.KEYED_FIELD)); + } + return range; + } + + private Range parseRange(XContentParser parser, ParseFieldMatcher parseFieldMatcher) throws IOException { + String key = null; + String from = null; + String to = null; + String mask = null; + + if (parser.currentToken() != Token.START_OBJECT) { + throw new ParsingException(parser.getTokenLocation(), "[ranges] must contain objects, but hit a " + parser.currentToken()); + } + while (parser.nextToken() != Token.END_OBJECT) { + if (parser.currentToken() == Token.FIELD_NAME) { + continue; + } + if (parseFieldMatcher.match(parser.currentName(), RangeAggregator.Range.KEY_FIELD)) { + key = parser.text(); + } else if (parseFieldMatcher.match(parser.currentName(), RangeAggregator.Range.FROM_FIELD)) { + from = parser.text(); + } else if (parseFieldMatcher.match(parser.currentName(), RangeAggregator.Range.TO_FIELD)) { + to = parser.text(); + } else if (parseFieldMatcher.match(parser.currentName(), MASK_FIELD)) { + mask = parser.text(); + } else { + throw new ParsingException(parser.getTokenLocation(), "Unexpected ip range parameter: [" + parser.currentName() + "]"); + } + } + if (mask != null) { + if (key == null) { + key = mask; + } + return new Range(key, mask); + } else { + return new Range(key, from, to); + } + } + + @Override + protected boolean token(String aggregationName, String currentFieldName, + Token token, XContentParser parser, + ParseFieldMatcher parseFieldMatcher, + Map otherOptions) throws IOException { + if (parseFieldMatcher.match(currentFieldName, RangeAggregator.RANGES_FIELD)) { + if (parser.currentToken() != Token.START_ARRAY) { + throw new ParsingException(parser.getTokenLocation(), "[ranges] must be passed as an array, but got a " + token); + } + List ranges = new ArrayList<>(); + while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) { + Range range = parseRange(parser, parseFieldMatcher); + ranges.add(range); + } + otherOptions.put(RangeAggregator.RANGES_FIELD, ranges); + return true; + } else if (parseFieldMatcher.match(parser.currentName(), RangeAggregator.KEYED_FIELD)) { + otherOptions.put(RangeAggregator.KEYED_FIELD, parser.booleanValue()); + return true; + } + return false; + } + +} diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/bucket/range/ipv4/IPv4RangeAggregatorBuilder.java b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/range/ipv4/IPv4RangeAggregatorBuilder.java deleted file mode 100644 index 6bafa6d5668..00000000000 --- a/core/src/main/java/org/elasticsearch/search/aggregations/bucket/range/ipv4/IPv4RangeAggregatorBuilder.java +++ /dev/null @@ -1,277 +0,0 @@ -/* - * 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.AggregatorFactory; -import org.elasticsearch.search.DocValueFormat; -import org.elasticsearch.search.aggregations.AggregatorFactories.Builder; -import org.elasticsearch.search.aggregations.bucket.range.AbstractRangeBuilder; -import org.elasticsearch.search.aggregations.bucket.range.RangeAggregator; -import org.elasticsearch.search.aggregations.support.AggregationContext; -import org.elasticsearch.search.aggregations.support.ValuesSourceConfig; -import org.elasticsearch.search.aggregations.support.ValuesSource.Numeric; -import org.elasticsearch.search.internal.SearchContext; - -import java.io.IOException; -import java.util.Objects; - -public class IPv4RangeAggregatorBuilder extends AbstractRangeBuilder { - public static final String NAME = InternalIPv4Range.TYPE.name(); - public static final ParseField AGGREGATION_NAME_FIELD = new ParseField(NAME); - - public IPv4RangeAggregatorBuilder(String name) { - super(name, InternalIPv4Range.FACTORY); - } - - /** - * Read from a stream. - */ - public IPv4RangeAggregatorBuilder(StreamInput in) throws IOException { - super(in, InternalIPv4Range.FACTORY, Range::new); - } - - @Override - public String getWriteableName() { - return NAME; - } - - /** - * Add a new range to this aggregation. - * - * @param key - * the key to use for this range in the response - * @param from - * the lower bound on the distances, inclusive - * @param to - * the upper bound on the distances, exclusive - */ - public IPv4RangeAggregatorBuilder addRange(String key, String from, String to) { - addRange(new Range(key, from, to)); - return this; - } - - /** - * Same as {@link #addMaskRange(String, String)} but uses the mask itself as - * a key. - */ - public IPv4RangeAggregatorBuilder addMaskRange(String key, String mask) { - return addRange(new Range(key, mask)); - } - - /** - * Same as {@link #addMaskRange(String, String)} but uses the mask itself as - * a key. - */ - public IPv4RangeAggregatorBuilder addMaskRange(String mask) { - return addRange(new Range(mask, mask)); - } - - /** - * Same as {@link #addRange(String, String, String)} but the key will be - * automatically generated. - */ - public IPv4RangeAggregatorBuilder addRange(String from, String to) { - return addRange(null, from, to); - } - - /** - * Same as {@link #addRange(String, String, String)} but there will be no - * lower bound. - */ - public IPv4RangeAggregatorBuilder addUnboundedTo(String key, String to) { - addRange(new Range(key, null, to)); - return this; - } - - /** - * Same as {@link #addUnboundedTo(String, String)} but the key will be - * generated automatically. - */ - public IPv4RangeAggregatorBuilder addUnboundedTo(String to) { - return addUnboundedTo(null, to); - } - - /** - * Same as {@link #addRange(String, String, String)} but there will be no - * upper bound. - */ - public IPv4RangeAggregatorBuilder addUnboundedFrom(String key, String from) { - addRange(new Range(key, from, null)); - return this; - } - - /** - * Same as {@link #addUnboundedFrom(String, String)} but the key will be - * generated automatically. - */ - public IPv4RangeAggregatorBuilder addUnboundedFrom(String from) { - return addUnboundedFrom(null, from); - } - - @Override - protected Ipv4RangeAggregatorFactory innerBuild(AggregationContext context, ValuesSourceConfig config, - AggregatorFactory parent, Builder subFactoriesBuilder) throws IOException { - return new Ipv4RangeAggregatorFactory(name, type, config, ranges, keyed, rangeFactory, context, parent, subFactoriesBuilder, - metaData); - } - - public static class Range extends RangeAggregator.Range { - static final ParseField MASK_FIELD = new ParseField("mask"); - - private final String cidr; - - public Range(String key, Double from, Double to) { - this(key, from, null, to, null, null); - } - - public Range(String key, String from, String to) { - this(key, null, from, null, to, null); - } - - public Range(String key, String cidr) { - this(key, null, null, null, null, cidr); - } - - private Range(String key, Double from, String fromAsStr, Double to, String toAsStr, String cidr) { - super(key, from, fromAsStr, to, toAsStr); - this.cidr = cidr; - } - - /** - * Read from a stream. - */ - public Range(StreamInput in) throws IOException { - super(in); - cidr = in.readOptionalString(); - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeOptionalString(cidr); - } - - public String mask() { - return cidr; - } - - @Override - public Range process(DocValueFormat parser, SearchContext context) { - assert parser != null; - Double from = this.from; - Double to = this.to; - String key = this.key; - if (fromAsStr != null) { - from = parser.parseDouble(fromAsStr, false, context.nowCallable()); - } - if (toAsStr != null) { - to = parser.parseDouble(toAsStr, false, context.nowCallable()); - } - if (cidr != null) { - 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 (this.key == null) { - key = cidr; - } - } - return new Range(key, from, to); - } - - public static 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 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 deleted file mode 100644 index f58df79c168..00000000000 --- a/core/src/main/java/org/elasticsearch/search/aggregations/bucket/range/ipv4/InternalIPv4Range.java +++ /dev/null @@ -1,164 +0,0 @@ -/* - * 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.io.stream.StreamInput; -import org.elasticsearch.search.DocValueFormat; -import org.elasticsearch.search.aggregations.AggregationStreams; -import org.elasticsearch.search.aggregations.InternalAggregation; -import org.elasticsearch.search.aggregations.InternalAggregations; -import org.elasticsearch.search.aggregations.bucket.BucketStreamContext; -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 java.io.IOException; -import java.util.List; -import java.util.Map; - -/** - * - */ -public class InternalIPv4Range extends InternalRange { - public static final long MAX_IP = 1L << 32; - - public final static Type TYPE = new Type("ip_range", "iprange"); - - private final static AggregationStreams.Stream STREAM = new AggregationStreams.Stream() { - @Override - public InternalIPv4Range readResult(StreamInput in) throws IOException { - InternalIPv4Range range = new InternalIPv4Range(); - range.readFrom(in); - return range; - } - }; - - private final static BucketStreams.Stream BUCKET_STREAM = new BucketStreams.Stream() { - @Override - public Bucket readResult(StreamInput in, BucketStreamContext context) throws IOException { - Bucket buckets = new Bucket(context.keyed()); - buckets.readFrom(in); - return buckets; - } - - @Override - public BucketStreamContext getBucketStreamContext(Bucket bucket) { - BucketStreamContext context = new BucketStreamContext(); - context.keyed(bucket.keyed()); - return context; - } - }; - - public static void registerStream() { - AggregationStreams.registerStream(STREAM, TYPE.stream()); - BucketStreams.registerStream(BUCKET_STREAM, TYPE.stream()); - } - - public static final Factory FACTORY = new Factory(); - - public static class Bucket extends InternalRange.Bucket { - - public Bucket(boolean keyed) { - super(keyed, DocValueFormat.IP); - } - - public Bucket(String key, double from, double to, long docCount, List aggregations, boolean keyed) { - super(key, from, to, docCount, new InternalAggregations(aggregations), keyed, DocValueFormat.IP); - } - - public Bucket(String key, double from, double to, long docCount, InternalAggregations aggregations, boolean keyed) { - super(key, from, to, docCount, aggregations, keyed, DocValueFormat.IP); - } - - @Override - public String getFromAsString() { - double from = ((Number) this.from).doubleValue(); - return Double.isInfinite(from) ? null : from == 0 ? null : DocValueFormat.IP.format(from); - } - - @Override - public String getToAsString() { - double to = ((Number) this.to).doubleValue(); - return Double.isInfinite(to) ? null : MAX_IP == to ? null : DocValueFormat.IP.format(to); - } - - @Override - protected InternalRange.Factory getFactory() { - return FACTORY; - } - - boolean keyed() { - return keyed; - } - } - - public static class Factory extends InternalRange.Factory { - - @Override - public Type type() { - return TYPE; - } - - @Override - public ValueType getValueType() { - return ValueType.IP; - } - - @Override - public InternalIPv4Range create(String name, List ranges, DocValueFormat formatter, boolean keyed, - List pipelineAggregators, Map metaData) { - return new InternalIPv4Range(name, ranges, keyed, pipelineAggregators, metaData); - } - - @Override - public InternalIPv4Range create(List ranges, InternalIPv4Range prototype) { - return new InternalIPv4Range(prototype.name, ranges, prototype.keyed, prototype.pipelineAggregators(), prototype.metaData); - } - - @Override - public Bucket createBucket(String key, double from, double to, long docCount, InternalAggregations aggregations, boolean keyed, - DocValueFormat formatter) { - return new Bucket(key, from, to, docCount, aggregations, keyed); - } - - @Override - public Bucket createBucket(InternalAggregations aggregations, Bucket prototype) { - return new Bucket(prototype.getKey(), ((Number) prototype.getFrom()).doubleValue(), ((Number) prototype.getTo()).doubleValue(), - prototype.getDocCount(), aggregations, prototype.getKeyed()); - } - } - - public InternalIPv4Range() {} // for serialization - - public InternalIPv4Range(String name, List ranges, boolean keyed, List pipelineAggregators, - Map metaData) { - super(name, ranges, DocValueFormat.IP, keyed, pipelineAggregators, metaData); - } - - @Override - public Type type() { - return TYPE; - } - - @Override - public InternalRange.Factory getFactory() { - return FACTORY; - } -} 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 deleted file mode 100644 index 901300c3bfd..00000000000 --- a/core/src/main/java/org/elasticsearch/search/aggregations/bucket/range/ipv4/IpRangeParser.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * 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.xcontent.XContentParser; -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.ValuesSourceType; - -import java.io.IOException; -import java.util.List; -import java.util.Map; - -/** - * - */ -public class IpRangeParser extends RangeParser { - - public IpRangeParser() { - super(true, false, false); - } - - @Override - protected Range parseRange(XContentParser parser, ParseFieldMatcher parseFieldMatcher) throws IOException { - return IPv4RangeAggregatorBuilder.Range.fromXContent(parser, parseFieldMatcher); - } - - @Override - protected IPv4RangeAggregatorBuilder createFactory(String aggregationName, ValuesSourceType valuesSourceType, - ValueType targetValueType, Map otherOptions) { - IPv4RangeAggregatorBuilder factory = new IPv4RangeAggregatorBuilder(aggregationName); - @SuppressWarnings("unchecked") - List ranges = (List) otherOptions - .get(RangeAggregator.RANGES_FIELD); - for (IPv4RangeAggregatorBuilder.Range range : ranges) { - factory.addRange(range); - } - Boolean keyed = (Boolean) otherOptions.get(RangeAggregator.KEYED_FIELD); - if (keyed != null) { - factory.keyed(keyed); - } - return factory; - } -} 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 deleted file mode 100644 index 1c059356c5d..00000000000 --- a/core/src/main/java/org/elasticsearch/search/aggregations/bucket/range/ipv4/Ipv4RangeAggregatorFactory.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * 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.search.aggregations.AggregatorFactories; -import org.elasticsearch.search.aggregations.AggregatorFactory; -import org.elasticsearch.search.aggregations.InternalAggregation.Type; -import org.elasticsearch.search.aggregations.bucket.range.AbstractRangeAggregatorFactory; -import org.elasticsearch.search.aggregations.bucket.range.InternalRange.Factory; -import org.elasticsearch.search.aggregations.support.ValuesSource.Numeric; -import org.elasticsearch.search.aggregations.support.AggregationContext; -import org.elasticsearch.search.aggregations.support.ValuesSourceConfig; - -import java.io.IOException; -import java.util.List; -import java.util.Map; - -public class Ipv4RangeAggregatorFactory - extends AbstractRangeAggregatorFactory { - - public Ipv4RangeAggregatorFactory(String name, Type type, ValuesSourceConfig config, - List ranges, boolean keyed, Factory rangeFactory, AggregationContext context, - AggregatorFactory parent, AggregatorFactories.Builder subFactoriesBuilder, Map metaData) throws IOException { - super(name, type, config, ranges, keyed, rangeFactory, context, parent, subFactoriesBuilder, metaData); - } - -} diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/support/ValueType.java b/core/src/main/java/org/elasticsearch/search/aggregations/support/ValueType.java index 021cdfb3a00..51f336a3422 100644 --- a/core/src/main/java/org/elasticsearch/search/aggregations/support/ValueType.java +++ b/core/src/main/java/org/elasticsearch/search/aggregations/support/ValueType.java @@ -70,12 +70,7 @@ public enum ValueType implements Writeable { return true; } }, - IP((byte) 6, "ip", "ip", ValuesSourceType.NUMERIC, IndexNumericFieldData.class, DocValueFormat.IP) { - @Override - public boolean isNumeric() { - return true; - } - }, + IP((byte) 6, "ip", "ip", ValuesSourceType.BYTES, IndexFieldData.class, DocValueFormat.IP), NUMERIC((byte) 7, "numeric", "numeric", ValuesSourceType.NUMERIC, IndexNumericFieldData.class, DocValueFormat.RAW) { @Override public boolean isNumeric() { diff --git a/core/src/test/java/org/elasticsearch/search/aggregations/BaseAggregationTestCase.java b/core/src/test/java/org/elasticsearch/search/aggregations/BaseAggregationTestCase.java index 4af87c982e1..2cd25e0fec7 100644 --- a/core/src/test/java/org/elasticsearch/search/aggregations/BaseAggregationTestCase.java +++ b/core/src/test/java/org/elasticsearch/search/aggregations/BaseAggregationTestCase.java @@ -86,9 +86,10 @@ public abstract class BaseAggregationTestCase> protected static final String DOUBLE_FIELD_NAME = "mapped_double"; protected static final String BOOLEAN_FIELD_NAME = "mapped_boolean"; protected static final String DATE_FIELD_NAME = "mapped_date"; + protected static final String IP_FIELD_NAME = "mapped_ip"; protected static final String OBJECT_FIELD_NAME = "mapped_object"; protected static final String[] mappedFieldNames = new String[]{STRING_FIELD_NAME, INT_FIELD_NAME, - DOUBLE_FIELD_NAME, BOOLEAN_FIELD_NAME, DATE_FIELD_NAME, OBJECT_FIELD_NAME}; + DOUBLE_FIELD_NAME, BOOLEAN_FIELD_NAME, DATE_FIELD_NAME, IP_FIELD_NAME, OBJECT_FIELD_NAME}; private static Injector injector; private static Index index; 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 deleted file mode 100644 index c72e220cf5e..00000000000 --- a/core/src/test/java/org/elasticsearch/search/aggregations/bucket/IPv4RangeTests.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * 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.LegacyIpFieldMapper; -import org.elasticsearch.search.aggregations.BaseAggregationTestCase; -import org.elasticsearch.search.aggregations.bucket.range.ipv4.IPv4RangeAggregatorBuilder; -import org.elasticsearch.search.aggregations.bucket.range.ipv4.IPv4RangeAggregatorBuilder.Range; - -public class IPv4RangeTests extends BaseAggregationTestCase { - - @Override - protected IPv4RangeAggregatorBuilder createTestAggregatorBuilder() { - int numRanges = randomIntBetween(1, 10); - IPv4RangeAggregatorBuilder factory = new IPv4RangeAggregatorBuilder("foo"); - 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()) { - factory.addRange(new Range(key, from, to)); - } else { - String fromAsStr = Double.isInfinite(from) ? null : LegacyIpFieldMapper.longToIp((long) from); - String toAsStr = Double.isInfinite(to) ? null : LegacyIpFieldMapper.longToIp((long) to); - factory.addRange(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); - factory.addRange(new Range(key, cidr)); - } - } - 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/IpRangeIT.java b/core/src/test/java/org/elasticsearch/search/aggregations/bucket/IpRangeIT.java new file mode 100644 index 00000000000..e89db4b02a1 --- /dev/null +++ b/core/src/test/java/org/elasticsearch/search/aggregations/bucket/IpRangeIT.java @@ -0,0 +1,268 @@ +/* + * 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 static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked; +import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertSearchResponse; +import static org.hamcrest.Matchers.containsString; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; + +import org.elasticsearch.action.search.SearchResponse; +import org.elasticsearch.common.inject.internal.Nullable; +import org.elasticsearch.plugins.Plugin; +import org.elasticsearch.script.AbstractSearchScript; +import org.elasticsearch.script.ExecutableScript; +import org.elasticsearch.script.NativeScriptFactory; +import org.elasticsearch.script.Script; +import org.elasticsearch.script.ScriptModule; +import org.elasticsearch.script.ScriptService.ScriptType; +import org.elasticsearch.search.aggregations.AggregationBuilders; +import org.elasticsearch.search.aggregations.AggregatorBuilder; +import org.elasticsearch.search.aggregations.bucket.range.Range; +import org.elasticsearch.test.ESIntegTestCase; + +@ESIntegTestCase.SuiteScopeTestCase +public class IpRangeIT extends ESIntegTestCase { + + @Override + protected Collection> nodePlugins() { + return Arrays.asList(DummyScriptPlugin.class); + } + + @Override + public void setupSuiteScopeCluster() throws Exception { + assertAcked(prepareCreate("idx") + .addMapping("type", "ip", "type=ip", "ips", "type=ip")); + + indexRandom(true, + client().prepareIndex("idx", "type", "1").setSource( + "ip", "192.168.1.7", + "ips", Arrays.asList("192.168.0.13", "192.168.1.2")), + client().prepareIndex("idx", "type", "2").setSource( + "ip", "192.168.1.10", + "ips", Arrays.asList("192.168.1.25", "192.168.1.28")), + client().prepareIndex("idx", "type", "3").setSource( + "ip", "2001:db8::ff00:42:8329", + "ips", Arrays.asList("2001:db8::ff00:42:8329", "2001:db8::ff00:42:8380"))); + + assertAcked(prepareCreate("idx_unmapped")); + } + + public void testSingleValuedField() { + SearchResponse rsp = client().prepareSearch("idx").addAggregation( + AggregationBuilders.ipRange("my_range") + .field("ip") + .addUnboundedTo("192.168.1.0") + .addRange("192.168.1.0", "192.168.1.10") + .addUnboundedFrom("192.168.1.10")).get(); + assertSearchResponse(rsp); + Range range = rsp.getAggregations().get("my_range"); + assertEquals(3, range.getBuckets().size()); + + Range.Bucket bucket1 = range.getBuckets().get(0); + assertNull(bucket1.getFrom()); + assertEquals("192.168.1.0", bucket1.getTo()); + assertEquals(0, bucket1.getDocCount()); + + Range.Bucket bucket2 = range.getBuckets().get(1); + assertEquals("192.168.1.0", bucket2.getFrom()); + assertEquals("192.168.1.10", bucket2.getTo()); + assertEquals(1, bucket2.getDocCount()); + + Range.Bucket bucket3 = range.getBuckets().get(2); + assertEquals("192.168.1.10", bucket3.getFrom()); + assertNull(bucket3.getTo()); + assertEquals(2, bucket3.getDocCount()); + } + + public void testMultiValuedField() { + SearchResponse rsp = client().prepareSearch("idx").addAggregation( + AggregationBuilders.ipRange("my_range") + .field("ips") + .addUnboundedTo("192.168.1.0") + .addRange("192.168.1.0", "192.168.1.10") + .addUnboundedFrom("192.168.1.10")).get(); + assertSearchResponse(rsp); + Range range = rsp.getAggregations().get("my_range"); + assertEquals(3, range.getBuckets().size()); + + Range.Bucket bucket1 = range.getBuckets().get(0); + assertNull(bucket1.getFrom()); + assertEquals("192.168.1.0", bucket1.getTo()); + assertEquals(1, bucket1.getDocCount()); + + Range.Bucket bucket2 = range.getBuckets().get(1); + assertEquals("192.168.1.0", bucket2.getFrom()); + assertEquals("192.168.1.10", bucket2.getTo()); + assertEquals(1, bucket2.getDocCount()); + + Range.Bucket bucket3 = range.getBuckets().get(2); + assertEquals("192.168.1.10", bucket3.getFrom()); + assertNull(bucket3.getTo()); + assertEquals(2, bucket3.getDocCount()); + } + + public void testIpMask() { + SearchResponse rsp = client().prepareSearch("idx").addAggregation( + AggregationBuilders.ipRange("my_range") + .field("ips") + .addMaskRange("::/0") + .addMaskRange("0.0.0.0/0") + .addMaskRange("2001:db8::/64")).get(); + assertSearchResponse(rsp); + Range range = rsp.getAggregations().get("my_range"); + assertEquals(3, range.getBuckets().size()); + + Range.Bucket bucket1 = range.getBuckets().get(0); + assertEquals("::/0", bucket1.getKey()); + assertEquals(3, bucket1.getDocCount()); + + Range.Bucket bucket2 = range.getBuckets().get(1); + assertEquals("0.0.0.0/0", bucket2.getKey()); + assertEquals(2, bucket2.getDocCount()); + + Range.Bucket bucket3 = range.getBuckets().get(2); + assertEquals("2001:db8::/64", bucket3.getKey()); + assertEquals(1, bucket3.getDocCount()); + } + + public void testPartiallyUnmapped() { + SearchResponse rsp = client().prepareSearch("idx", "idx_unmapped").addAggregation( + AggregationBuilders.ipRange("my_range") + .field("ip") + .addUnboundedTo("192.168.1.0") + .addRange("192.168.1.0", "192.168.1.10") + .addUnboundedFrom("192.168.1.10")).get(); + assertSearchResponse(rsp); + Range range = rsp.getAggregations().get("my_range"); + assertEquals(3, range.getBuckets().size()); + + Range.Bucket bucket1 = range.getBuckets().get(0); + assertNull(bucket1.getFrom()); + assertEquals("192.168.1.0", bucket1.getTo()); + assertEquals(0, bucket1.getDocCount()); + + Range.Bucket bucket2 = range.getBuckets().get(1); + assertEquals("192.168.1.0", bucket2.getFrom()); + assertEquals("192.168.1.10", bucket2.getTo()); + assertEquals(1, bucket2.getDocCount()); + + Range.Bucket bucket3 = range.getBuckets().get(2); + assertEquals("192.168.1.10", bucket3.getFrom()); + assertNull(bucket3.getTo()); + assertEquals(2, bucket3.getDocCount()); + } + + public void testUnmapped() { + SearchResponse rsp = client().prepareSearch("idx_unmapped").addAggregation( + AggregationBuilders.ipRange("my_range") + .field("ip") + .addUnboundedTo("192.168.1.0") + .addRange("192.168.1.0", "192.168.1.10") + .addUnboundedFrom("192.168.1.10")).get(); + assertSearchResponse(rsp); + Range range = rsp.getAggregations().get("my_range"); + assertEquals(3, range.getBuckets().size()); + + Range.Bucket bucket1 = range.getBuckets().get(0); + assertNull(bucket1.getFrom()); + assertEquals("192.168.1.0", bucket1.getTo()); + assertEquals(0, bucket1.getDocCount()); + + Range.Bucket bucket2 = range.getBuckets().get(1); + assertEquals("192.168.1.0", bucket2.getFrom()); + assertEquals("192.168.1.10", bucket2.getTo()); + assertEquals(0, bucket2.getDocCount()); + + Range.Bucket bucket3 = range.getBuckets().get(2); + assertEquals("192.168.1.10", bucket3.getFrom()); + assertNull(bucket3.getTo()); + assertEquals(0, bucket3.getDocCount()); + } + + public void testRejectsScript() { + IllegalArgumentException e = expectThrows(IllegalArgumentException.class, + () -> client().prepareSearch("idx").addAggregation( + AggregationBuilders.ipRange("my_range") + .script(new Script(DummyScript.NAME, ScriptType.INLINE, "native", Collections.emptyMap())) ).get()); + assertThat(e.getMessage(), containsString("[ip_range] does not support scripts")); + } + + public void testRejectsValueScript() { + IllegalArgumentException e = expectThrows(IllegalArgumentException.class, + () -> client().prepareSearch("idx").addAggregation( + AggregationBuilders.ipRange("my_range") + .field("ip") + .script(new Script(DummyScript.NAME, ScriptType.INLINE, "native", Collections.emptyMap())) ).get()); + assertThat(e.getMessage(), containsString("[ip_range] does not support scripts")); + } + + public static class DummyScriptPlugin extends Plugin { + + @Override + public String name() { + return "DummyScriptPlugin"; + } + + @Override + public String description() { + return "A mock script plugin."; + } + + public void onModule(ScriptModule module) { + module.registerScript(DummyScript.NAME, DummyScriptFactory.class); + } + } + + public static class DummyScriptFactory implements NativeScriptFactory { + public DummyScriptFactory() {} + @Override + public ExecutableScript newScript(@Nullable Map params) { + return new DummyScript(); + } + @Override + public boolean needsScores() { + return false; + } + } + + private static class DummyScript extends AbstractSearchScript { + + public static final String NAME = "dummy"; + + @Override + public Object run() { + return null; + } + } + + public static void main(String[] args) { + AggregatorBuilder aggregation = + AggregationBuilders + .ipRange("agg") + .field("ip") + .addUnboundedTo("192.168.1.0") // from -infinity to 192.168.1.0 (excluded) + .addRange("192.168.1.0", "192.168.2.0") // from 192.168.1.0 to 192.168.2.0 (excluded) + .addUnboundedFrom("192.168.2.0"); // from 192.168.2.0 to +infinity + } +} diff --git a/core/src/test/java/org/elasticsearch/search/aggregations/bucket/IpRangeTests.java b/core/src/test/java/org/elasticsearch/search/aggregations/bucket/IpRangeTests.java new file mode 100644 index 00000000000..db31f576e0c --- /dev/null +++ b/core/src/test/java/org/elasticsearch/search/aggregations/bucket/IpRangeTests.java @@ -0,0 +1,90 @@ +/* + * 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 java.net.InetAddress; +import java.net.UnknownHostException; + +import org.elasticsearch.common.network.NetworkAddress; +import org.elasticsearch.search.aggregations.BaseAggregationTestCase; +import org.elasticsearch.search.aggregations.bucket.range.ip.IpRangeAggregatorBuilder; + +public class IpRangeTests extends BaseAggregationTestCase { + + private static String randomIp(boolean v4) { + try { + if (v4) { + byte[] ipv4 = new byte[4]; + random().nextBytes(ipv4); + return NetworkAddress.format(InetAddress.getByAddress(ipv4)); + } else { + byte[] ipv6 = new byte[16]; + random().nextBytes(ipv6); + return NetworkAddress.format(InetAddress.getByAddress(ipv6)); + } + } catch (UnknownHostException e) { + throw new AssertionError(); + } + } + + @Override + protected IpRangeAggregatorBuilder createTestAggregatorBuilder() { + int numRanges = randomIntBetween(1, 10); + IpRangeAggregatorBuilder factory = new IpRangeAggregatorBuilder("foo"); + for (int i = 0; i < numRanges; i++) { + String key = null; + if (randomBoolean()) { + key = randomAsciiOfLengthBetween(1, 20); + } + switch (randomInt(3)) { + case 0: + boolean v4 = randomBoolean(); + int prefixLength; + if (v4) { + prefixLength = randomInt(32); + } else { + prefixLength = randomInt(128); + } + factory.addMaskRange(key, randomIp(v4) + "/" + prefixLength); + break; + case 1: + factory.addUnboundedFrom(key, randomIp(randomBoolean())); + break; + case 2: + factory.addUnboundedTo(key, randomIp(randomBoolean())); + break; + case 3: + factory.addRange(key, randomIp(randomBoolean()), randomIp(randomBoolean())); + break; + default: + fail(); + } + } + factory.field(IP_FIELD_NAME); + if (randomBoolean()) { + factory.keyed(randomBoolean()); + } + if (randomBoolean()) { + factory.missing(randomIp(randomBoolean())); + } + return factory; + } + +} diff --git a/core/src/test/java/org/elasticsearch/search/aggregations/bucket/range/BinaryRangeAggregatorTests.java b/core/src/test/java/org/elasticsearch/search/aggregations/bucket/range/BinaryRangeAggregatorTests.java new file mode 100644 index 00000000000..b3ad7771bec --- /dev/null +++ b/core/src/test/java/org/elasticsearch/search/aggregations/bucket/range/BinaryRangeAggregatorTests.java @@ -0,0 +1,142 @@ +/* + * 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; + +import java.io.IOException; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import org.apache.lucene.index.SortedSetDocValues; +import org.apache.lucene.util.BytesRef; +import org.apache.lucene.util.TestUtil; +import org.elasticsearch.search.aggregations.LeafBucketCollector; +import org.elasticsearch.search.aggregations.bucket.range.BinaryRangeAggregator.SortedSetRangeLeafCollector; +import org.elasticsearch.test.ESTestCase; + +import com.carrotsearch.hppc.LongHashSet; + +public class BinaryRangeAggregatorTests extends ESTestCase { + + private static class FakeSortedSetDocValues extends SortedSetDocValues { + + private final BytesRef[] terms; + long[] ords; + private int i; + + FakeSortedSetDocValues(BytesRef[] terms) { + this.terms = terms; + } + + @Override + public void setDocument(int docID) { + i = 0; + } + + @Override + public long nextOrd() { + if (i == ords.length) { + return NO_MORE_ORDS; + } + return ords[i++]; + } + + @Override + public BytesRef lookupOrd(long ord) { + return terms[(int) ord]; + } + + @Override + public long getValueCount() { + return terms.length; + } + + } + + private void doTestSortedSetRangeLeafCollector(int maxNumValuesPerDoc) throws Exception { + final Set termSet = new HashSet<>(); + final int numTerms = TestUtil.nextInt(random(), maxNumValuesPerDoc, 100); + while (termSet.size() < numTerms) { + termSet.add(new BytesRef(TestUtil.randomSimpleString(random(), randomInt(2)))); + } + final BytesRef[] terms = termSet.toArray(new BytesRef[0]); + Arrays.sort(terms); + + final int numRanges = randomIntBetween(1, 10); + BinaryRangeAggregator.Range[] ranges = new BinaryRangeAggregator.Range[numRanges]; + for (int i = 0; i < numRanges; ++i) { + ranges[i] = new BinaryRangeAggregator.Range(Integer.toString(i), + randomBoolean() ? null : new BytesRef(TestUtil.randomSimpleString(random(), randomInt(2))), + randomBoolean() ? null : new BytesRef(TestUtil.randomSimpleString(random(), randomInt(2)))); + } + Arrays.sort(ranges, BinaryRangeAggregator.RANGE_COMPARATOR); + + FakeSortedSetDocValues values = new FakeSortedSetDocValues(terms); + final int[] counts = new int[ranges.length]; + SortedSetRangeLeafCollector collector = new SortedSetRangeLeafCollector(values, ranges, null) { + @Override + protected void doCollect(LeafBucketCollector sub, int doc, long bucket) throws IOException { + counts[(int) bucket]++; + } + }; + + final int[] expectedCounts = new int[ranges.length]; + final int maxDoc = randomIntBetween(5, 10); + for (int doc = 0; doc < maxDoc; ++doc) { + LongHashSet ordinalSet = new LongHashSet(); + final int numValues = randomInt(maxNumValuesPerDoc); + while (ordinalSet.size() < numValues) { + ordinalSet.add(random().nextInt(terms.length)); + } + final long[] ords = ordinalSet.toArray(); + Arrays.sort(ords); + values.ords = ords; + + // simulate aggregation + collector.collect(doc); + + // now do it the naive way + for (int i = 0; i < ranges.length; ++i) { + for (long ord : ords) { + BytesRef term = terms[(int) ord]; + if ((ranges[i].from == null || ranges[i].from.compareTo(term) <= 0) + && (ranges[i].to == null || ranges[i].to.compareTo(term) > 0)) { + expectedCounts[i]++; + break; + } + } + } + } + assertArrayEquals(expectedCounts, counts); + } + + public void testSortedSetRangeLeafCollectorSingleValued() throws Exception { + final int iters = randomInt(10); + for (int i = 0; i < iters; ++i) { + doTestSortedSetRangeLeafCollector(1); + } + } + + public void testSortedSetRangeLeafCollectorMultiValued() throws Exception { + final int iters = randomInt(10); + for (int i = 0; i < iters; ++i) { + doTestSortedSetRangeLeafCollector(5); + } + } +} diff --git a/docs/java-api/aggregations/bucket/iprange-aggregation.asciidoc b/docs/java-api/aggregations/bucket/iprange-aggregation.asciidoc index eb095a2898c..7c1909298db 100644 --- a/docs/java-api/aggregations/bucket/iprange-aggregation.asciidoc +++ b/docs/java-api/aggregations/bucket/iprange-aggregation.asciidoc @@ -12,7 +12,7 @@ Here is an example on how to create the aggregation request: [source,java] -------------------------------------------------- -AggregationBuilder aggregation = +AggregatorBuilder aggregation = AggregationBuilders .ipRange("agg") .field("ip") @@ -25,7 +25,7 @@ Note that you could also use ip masks as ranges: [source,java] -------------------------------------------------- -AggregationBuilder aggregation = +AggregatorBuilder aggregation = AggregationBuilders .ipRange("agg") .field("ip") diff --git a/docs/reference/aggregations/bucket/iprange-aggregation.asciidoc b/docs/reference/aggregations/bucket/iprange-aggregation.asciidoc index 6d06743644b..bb20b18663e 100644 --- a/docs/reference/aggregations/bucket/iprange-aggregation.asciidoc +++ b/docs/reference/aggregations/bucket/iprange-aggregation.asciidoc @@ -1,7 +1,7 @@ [[search-aggregations-bucket-iprange-aggregation]] -=== IPv4 Range Aggregation +=== IP Range Aggregation -Just like the dedicated <> range aggregation, there is also a dedicated range aggregation for IPv4 typed fields: +Just like the dedicated <> range aggregation, there is also a dedicated range aggregation for IP typed fields: Example: @@ -33,13 +33,11 @@ Response: "ip_ranges": { "buckets" : [ { - "to": 167772165, - "to_as_string": "10.0.0.5", + "to": "10.0.0.5", "doc_count": 4 }, { - "from": 167772165, - "from_as_string": "10.0.0.5", + "from": "10.0.0.5", "doc_count": 6 } ] @@ -77,22 +75,18 @@ Response: "buckets": [ { "key": "10.0.0.0/25", - "from": 1.6777216E+8, - "from_as_string": "10.0.0.0", - "to": 167772287, - "to_as_string": "10.0.0.127", + "from": "10.0.0.0", + "to": "10.0.0.127", "doc_count": 127 }, { "key": "10.0.0.127/25", - "from": 1.6777216E+8, - "from_as_string": "10.0.0.0", - "to": 167772287, - "to_as_string": "10.0.0.127", + "from": "10.0.0.0", + "to": "10.0.0.127", "doc_count": 127 } ] } } } --------------------------------------------------- \ No newline at end of file +-------------------------------------------------- diff --git a/docs/reference/migration/migrate_5_0/aggregations.asciidoc b/docs/reference/migration/migrate_5_0/aggregations.asciidoc index 60c1060f159..d90e429732b 100644 --- a/docs/reference/migration/migrate_5_0/aggregations.asciidoc +++ b/docs/reference/migration/migrate_5_0/aggregations.asciidoc @@ -13,3 +13,10 @@ It is recommended to use <> fields instead, either directly or through a <> if the numeric representation is still needed for sorting, range queries or numeric aggregations like <>. + +==== `ip_range` aggregations + +Now that Elasticsearch supports `ipv6`, `ip` addresses are encoded in the index +using a binary representation rather than a numeric representation. As a +consequence, the output of `ip_range` aggregations does not give numeric values +for `from` and `to` anymore. diff --git a/modules/lang-groovy/src/test/java/org/elasticsearch/messy/tests/IPv4RangeTests.java b/modules/lang-groovy/src/test/java/org/elasticsearch/messy/tests/IPv4RangeTests.java deleted file mode 100644 index 23ecfad39b8..00000000000 --- a/modules/lang-groovy/src/test/java/org/elasticsearch/messy/tests/IPv4RangeTests.java +++ /dev/null @@ -1,717 +0,0 @@ -/* - * 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.messy.tests; - -import org.apache.lucene.util.LuceneTestCase.AwaitsFix; -import org.elasticsearch.action.index.IndexRequestBuilder; -import org.elasticsearch.action.search.SearchResponse; -import org.elasticsearch.index.mapper.ip.LegacyIpFieldMapper; -import org.elasticsearch.plugins.Plugin; -import org.elasticsearch.script.Script; -import org.elasticsearch.script.groovy.GroovyPlugin; -import org.elasticsearch.search.aggregations.bucket.histogram.Histogram; -import org.elasticsearch.search.aggregations.bucket.range.Range; -import org.elasticsearch.search.aggregations.bucket.range.Range.Bucket; -import org.elasticsearch.search.aggregations.metrics.sum.Sum; -import org.elasticsearch.test.ESIntegTestCase; -import org.hamcrest.Matchers; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; - -import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; -import static org.elasticsearch.index.query.QueryBuilders.matchAllQuery; -import static org.elasticsearch.search.aggregations.AggregationBuilders.histogram; -import static org.elasticsearch.search.aggregations.AggregationBuilders.ipRange; -import static org.elasticsearch.search.aggregations.AggregationBuilders.sum; -import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked; -import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertSearchResponse; -import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.core.IsNull.notNullValue; -import static org.hamcrest.core.IsNull.nullValue; - -/** - * - */ -@AwaitsFix(bugUrl="https://github.com/elastic/elasticsearch/issues/17700") -@ESIntegTestCase.SuiteScopeTestCase -public class IPv4RangeTests extends ESIntegTestCase { - - @Override - protected Collection> nodePlugins() { - return Collections.singleton(GroovyPlugin.class); - } - - @Override - public void setupSuiteScopeCluster() throws Exception { - { - assertAcked(prepareCreate("idx") - .addMapping("type", "ip", "type=ip", "ips", "type=ip")); - IndexRequestBuilder[] builders = new IndexRequestBuilder[255]; // TODO randomize the size? - // TODO randomize the values in the docs? - for (int i = 0; i < builders.length; i++) { - builders[i] = client().prepareIndex("idx", "type").setSource(jsonBuilder() - .startObject() - .field("ip", "10.0.0." + (i)) - .startArray("ips").value("10.0.0." + i).value("10.0.0." + (i + 1)).endArray() - .field("value", (i < 100 ? 1 : i < 200 ? 2 : 3)) // 100 1's, 100 2's, and 55 3's - .endObject()); - } - indexRandom(true, builders); - createIndex("idx_unmapped"); - } - { - assertAcked(prepareCreate("empty_bucket_idx").addMapping("type", "value", "type=integer", "ip", "type=ip")); - List builders = new ArrayList<>(); - for (int i = 0; i < 2; i++) { - builders.add(client().prepareIndex("empty_bucket_idx", "type", "" + i).setSource(jsonBuilder() - .startObject() - .field("value", i * 2) - .field("ip", "10.0.0.5") - .endObject())); - } - indexRandom(true, builders.toArray(new IndexRequestBuilder[builders.size()])); - } - { - assertAcked(prepareCreate("range_idx") - .addMapping("type", "ip", "type=ip", "ips", "type=ip")); - IndexRequestBuilder[] builders = new IndexRequestBuilder[4]; - - builders[0] = client().prepareIndex("range_idx", "type").setSource(jsonBuilder() - .startObject() - .field("ip", "0.0.0.0") - .endObject()); - - builders[1] = client().prepareIndex("range_idx", "type").setSource(jsonBuilder() - .startObject() - .field("ip", "0.0.0.255") - .endObject()); - - builders[2] = client().prepareIndex("range_idx", "type").setSource(jsonBuilder() - .startObject() - .field("ip", "255.255.255.0") - .endObject()); - - builders[3] = client().prepareIndex("range_idx", "type").setSource(jsonBuilder() - .startObject() - .field("ip", "255.255.255.255") - .endObject()); - - indexRandom(true, builders); - } - ensureSearchable(); - } - - public void testSingleValueField() throws Exception { - SearchResponse response = client().prepareSearch("idx") - .addAggregation(ipRange("range") - .field("ip") - .addUnboundedTo("10.0.0.100") - .addRange("10.0.0.100", "10.0.0.200") - .addUnboundedFrom("10.0.0.200")) - .execute().actionGet(); - - assertSearchResponse(response); - - - Range range = response.getAggregations().get("range"); - assertThat(range, notNullValue()); - assertThat(range.getName(), equalTo("range")); - List buckets = range.getBuckets(); - assertThat(buckets.size(), equalTo(3)); - - Range.Bucket bucket = buckets.get(0); - assertThat(bucket, notNullValue()); - assertThat((String) bucket.getKey(), equalTo("*-10.0.0.100")); - assertThat(((Number) bucket.getFrom()).doubleValue(), equalTo(Double.NEGATIVE_INFINITY)); - assertThat(bucket.getFromAsString(), nullValue()); - assertThat(bucket.getToAsString(), equalTo("10.0.0.100")); - assertThat(((Number) bucket.getTo()).doubleValue(), equalTo((double) LegacyIpFieldMapper.ipToLong("10.0.0.100"))); - assertThat(bucket.getDocCount(), equalTo(100L)); - - bucket = buckets.get(1); - assertThat(bucket, notNullValue()); - assertThat((String) bucket.getKey(), equalTo("10.0.0.100-10.0.0.200")); - assertThat(bucket.getFromAsString(), equalTo("10.0.0.100")); - assertThat(((Number) bucket.getFrom()).doubleValue(), equalTo((double) LegacyIpFieldMapper.ipToLong("10.0.0.100"))); - assertThat(bucket.getToAsString(), equalTo("10.0.0.200")); - assertThat(((Number) bucket.getTo()).doubleValue(), equalTo((double) LegacyIpFieldMapper.ipToLong("10.0.0.200"))); - assertThat(bucket.getDocCount(), equalTo(100L)); - - bucket = buckets.get(2); - assertThat(bucket, notNullValue()); - assertThat((String) bucket.getKey(), equalTo("10.0.0.200-*")); - assertThat(bucket.getFromAsString(), equalTo("10.0.0.200")); - assertThat(((Number) bucket.getFrom()).doubleValue(), equalTo((double) LegacyIpFieldMapper.ipToLong("10.0.0.200"))); - assertThat(((Number) bucket.getTo()).doubleValue(), equalTo(Double.POSITIVE_INFINITY)); - assertThat(bucket.getToAsString(), nullValue()); - assertThat(bucket.getDocCount(), equalTo(55L)); - } - - public void testSingleValueFieldWithMaskRange() throws Exception { - SearchResponse response = client().prepareSearch("idx") - .addAggregation(ipRange("range") - .field("ip") - .addMaskRange("10.0.0.0/25") - .addMaskRange("10.0.0.128/25")) - .execute().actionGet(); - - assertSearchResponse(response); - - - Range range = response.getAggregations().get("range"); - assertThat(range, notNullValue()); - assertThat(range.getName(), equalTo("range")); - List buckets = range.getBuckets(); - assertThat(range.getBuckets().size(), equalTo(2)); - - Range.Bucket bucket = buckets.get(0); - assertThat(bucket, notNullValue()); - assertThat((String) bucket.getKey(), equalTo("10.0.0.0/25")); - assertThat(((Number) bucket.getFrom()).doubleValue(), equalTo((double) LegacyIpFieldMapper.ipToLong("10.0.0.0"))); - assertThat(bucket.getFromAsString(), equalTo("10.0.0.0")); - assertThat(((Number) bucket.getTo()).doubleValue(), equalTo((double) LegacyIpFieldMapper.ipToLong("10.0.0.128"))); - assertThat(bucket.getToAsString(), equalTo("10.0.0.128")); - assertThat(bucket.getDocCount(), equalTo(128L)); - - bucket = buckets.get(1); - assertThat(bucket, notNullValue()); - assertThat((String) bucket.getKey(), equalTo("10.0.0.128/25")); - assertThat((long) ((Number) bucket.getFrom()).doubleValue(), equalTo(LegacyIpFieldMapper.ipToLong("10.0.0.128"))); - assertThat(bucket.getFromAsString(), equalTo("10.0.0.128")); - assertThat((long) ((Number) bucket.getTo()).doubleValue(), equalTo(LegacyIpFieldMapper.ipToLong("10.0.1.0"))); // range is exclusive on the to side - assertThat(bucket.getToAsString(), equalTo("10.0.1.0")); - assertThat(bucket.getDocCount(), equalTo(127L)); // include 10.0.0.128 - } - - public void testSingleValueFieldWithCustomKey() throws Exception { - SearchResponse response = client().prepareSearch("idx") - .addAggregation(ipRange("range") - .field("ip") - .addUnboundedTo("r1", "10.0.0.100") - .addRange("r2", "10.0.0.100", "10.0.0.200") - .addUnboundedFrom("r3", "10.0.0.200")) - .execute().actionGet(); - - assertSearchResponse(response); - - - Range range = response.getAggregations().get("range"); - assertThat(range, notNullValue()); - assertThat(range.getName(), equalTo("range")); - List buckets = range.getBuckets(); - assertThat(range.getBuckets().size(), equalTo(3)); - - Range.Bucket bucket = buckets.get(0); - assertThat(bucket, notNullValue()); - assertThat((String) bucket.getKey(), equalTo("r1")); - assertThat(((Number) bucket.getFrom()).doubleValue(), equalTo(Double.NEGATIVE_INFINITY)); - assertThat(bucket.getFromAsString(), nullValue()); - assertThat(bucket.getToAsString(), equalTo("10.0.0.100")); - assertThat(((Number) bucket.getTo()).doubleValue(), equalTo((double) LegacyIpFieldMapper.ipToLong("10.0.0.100"))); - assertThat(bucket.getDocCount(), equalTo(100L)); - - bucket = buckets.get(1); - assertThat(bucket, notNullValue()); - assertThat((String) bucket.getKey(), equalTo("r2")); - assertThat(bucket.getFromAsString(), equalTo("10.0.0.100")); - assertThat(((Number) bucket.getFrom()).doubleValue(), equalTo((double) LegacyIpFieldMapper.ipToLong("10.0.0.100"))); - assertThat(bucket.getToAsString(), equalTo("10.0.0.200")); - assertThat(((Number) bucket.getTo()).doubleValue(), equalTo((double) LegacyIpFieldMapper.ipToLong("10.0.0.200"))); - assertThat(bucket.getDocCount(), equalTo(100L)); - - bucket = buckets.get(2); - assertThat(bucket, notNullValue()); - assertThat((String) bucket.getKey(), equalTo("r3")); - assertThat(bucket.getFromAsString(), equalTo("10.0.0.200")); - assertThat(((Number) bucket.getFrom()).doubleValue(), equalTo((double) LegacyIpFieldMapper.ipToLong("10.0.0.200"))); - assertThat(((Number) bucket.getTo()).doubleValue(), equalTo(Double.POSITIVE_INFINITY)); - assertThat(bucket.getToAsString(), nullValue()); - assertThat(bucket.getDocCount(), equalTo(55L)); - } - - public void testSingleValuedFieldWithSubAggregation() throws Exception { - SearchResponse response = client().prepareSearch("idx") - .addAggregation(ipRange("range") - .field("ip") - .addUnboundedTo("10.0.0.100") - .addRange("10.0.0.100", "10.0.0.200") - .addUnboundedFrom("10.0.0.200") - .subAggregation(sum("sum").field("value"))) - .execute().actionGet(); - - assertSearchResponse(response); - - - Range range = response.getAggregations().get("range"); - assertThat(range, notNullValue()); - assertThat(range.getName(), equalTo("range")); - List buckets = range.getBuckets(); - assertThat(range.getBuckets().size(), equalTo(3)); - Object[] propertiesKeys = (Object[]) range.getProperty("_key"); - Object[] propertiesDocCounts = (Object[]) range.getProperty("_count"); - Object[] propertiesCounts = (Object[]) range.getProperty("sum.value"); - - Range.Bucket bucket = buckets.get(0); - assertThat(bucket, notNullValue()); - assertThat((String) bucket.getKey(), equalTo("*-10.0.0.100")); - assertThat(((Number) bucket.getFrom()).doubleValue(), equalTo(Double.NEGATIVE_INFINITY)); - assertThat(bucket.getFromAsString(), nullValue()); - assertThat(bucket.getToAsString(), equalTo("10.0.0.100")); - assertThat(((Number) bucket.getTo()).doubleValue(), equalTo((double) LegacyIpFieldMapper.ipToLong("10.0.0.100"))); - assertThat(bucket.getDocCount(), equalTo(100L)); - Sum sum = bucket.getAggregations().get("sum"); - assertThat(sum, notNullValue()); - assertThat(sum.getValue(), equalTo((double) 100)); - assertThat((String) propertiesKeys[0], equalTo("*-10.0.0.100")); - assertThat((long) propertiesDocCounts[0], equalTo(100L)); - assertThat((double) propertiesCounts[0], equalTo((double) 100)); - - bucket = buckets.get(1); - assertThat(bucket, notNullValue()); - assertThat((String) bucket.getKey(), equalTo("10.0.0.100-10.0.0.200")); - assertThat(bucket.getFromAsString(), equalTo("10.0.0.100")); - assertThat(((Number) bucket.getFrom()).doubleValue(), equalTo((double) LegacyIpFieldMapper.ipToLong("10.0.0.100"))); - assertThat(bucket.getToAsString(), equalTo("10.0.0.200")); - assertThat(((Number) bucket.getTo()).doubleValue(), equalTo((double) LegacyIpFieldMapper.ipToLong("10.0.0.200"))); - assertThat(bucket.getDocCount(), equalTo(100L)); - sum = bucket.getAggregations().get("sum"); - assertThat(sum, notNullValue()); - assertThat(sum.getValue(), equalTo((double) 200)); - assertThat((String) propertiesKeys[1], equalTo("10.0.0.100-10.0.0.200")); - assertThat((long) propertiesDocCounts[1], equalTo(100L)); - assertThat((double) propertiesCounts[1], equalTo((double) 200)); - - bucket = buckets.get(2); - assertThat(bucket, notNullValue()); - assertThat((String) bucket.getKey(), equalTo("10.0.0.200-*")); - assertThat(bucket.getFromAsString(), equalTo("10.0.0.200")); - assertThat(((Number) bucket.getFrom()).doubleValue(), equalTo((double) LegacyIpFieldMapper.ipToLong("10.0.0.200"))); - assertThat(((Number) bucket.getTo()).doubleValue(), equalTo(Double.POSITIVE_INFINITY)); - assertThat(bucket.getToAsString(), nullValue()); - assertThat(bucket.getDocCount(), equalTo(55L)); - sum = bucket.getAggregations().get("sum"); - assertThat(sum, notNullValue()); - assertThat(sum.getValue(), equalTo((double) 55*3)); - assertThat((String) propertiesKeys[2], equalTo("10.0.0.200-*")); - assertThat((long) propertiesDocCounts[2], equalTo(55L)); - assertThat((double) propertiesCounts[2], equalTo((double) 55 * 3)); - } - - public void testSingleValuedFieldWithValueScript() throws Exception { - SearchResponse response = client() - .prepareSearch("idx") - .addAggregation( - ipRange("range").field("ip").script(new Script("_value")).addUnboundedTo("10.0.0.100") - .addRange("10.0.0.100", "10.0.0.200").addUnboundedFrom("10.0.0.200")).execute().actionGet(); - - assertSearchResponse(response); - - Range range = response.getAggregations().get("range"); - assertThat(range, notNullValue()); - assertThat(range.getName(), equalTo("range")); - List buckets = range.getBuckets(); - assertThat(range.getBuckets().size(), equalTo(3)); - - Range.Bucket bucket = buckets.get(0); - assertThat(bucket, notNullValue()); - assertThat((String) bucket.getKey(), equalTo("*-10.0.0.100")); - assertThat(((Number) bucket.getFrom()).doubleValue(), equalTo(Double.NEGATIVE_INFINITY)); - assertThat(bucket.getFromAsString(), nullValue()); - assertThat(bucket.getToAsString(), equalTo("10.0.0.100")); - assertThat(((Number) bucket.getTo()).doubleValue(), equalTo((double) LegacyIpFieldMapper.ipToLong("10.0.0.100"))); - assertThat(bucket.getDocCount(), equalTo(100L)); - - bucket = buckets.get(1); - assertThat(bucket, notNullValue()); - assertThat((String) bucket.getKey(), equalTo("10.0.0.100-10.0.0.200")); - assertThat(bucket.getFromAsString(), equalTo("10.0.0.100")); - assertThat(((Number) bucket.getFrom()).doubleValue(), equalTo((double) LegacyIpFieldMapper.ipToLong("10.0.0.100"))); - assertThat(bucket.getToAsString(), equalTo("10.0.0.200")); - assertThat(((Number) bucket.getTo()).doubleValue(), equalTo((double) LegacyIpFieldMapper.ipToLong("10.0.0.200"))); - assertThat(bucket.getDocCount(), equalTo(100L)); - - bucket = buckets.get(2); - assertThat(bucket, notNullValue()); - assertThat((String) bucket.getKey(), equalTo("10.0.0.200-*")); - assertThat(bucket.getFromAsString(), equalTo("10.0.0.200")); - assertThat(((Number) bucket.getFrom()).doubleValue(), equalTo((double) LegacyIpFieldMapper.ipToLong("10.0.0.200"))); - assertThat(((Number) bucket.getTo()).doubleValue(), equalTo(Double.POSITIVE_INFINITY)); - assertThat(bucket.getToAsString(), nullValue()); - assertThat(bucket.getDocCount(), equalTo(55L)); - } - - /* - [0, 1] - [1, 2] - [2, 3] - ... - [99, 100] - [100, 101] - [101, 102] - ... - [199, 200] - [200, 201] - [201, 202] - ... - [254, 255] - [255, 256] - */ - - public void testMultiValuedField() throws Exception { - SearchResponse response = client().prepareSearch("idx") - .addAggregation(ipRange("range") - .field("ips") - .addUnboundedTo("10.0.0.100") - .addRange("10.0.0.100", "10.0.0.200") - .addUnboundedFrom("10.0.0.200")) - .execute().actionGet(); - - assertSearchResponse(response); - - - Range range = response.getAggregations().get("range"); - assertThat(range, notNullValue()); - assertThat(range.getName(), equalTo("range")); - List buckets = range.getBuckets(); - assertThat(range.getBuckets().size(), equalTo(3)); - - Range.Bucket bucket = buckets.get(0); - assertThat(bucket, notNullValue()); - assertThat((String) bucket.getKey(), equalTo("*-10.0.0.100")); - assertThat(((Number) bucket.getFrom()).doubleValue(), equalTo(Double.NEGATIVE_INFINITY)); - assertThat(bucket.getFromAsString(), nullValue()); - assertThat(bucket.getToAsString(), equalTo("10.0.0.100")); - assertThat(((Number) bucket.getTo()).doubleValue(), equalTo((double) LegacyIpFieldMapper.ipToLong("10.0.0.100"))); - assertThat(bucket.getDocCount(), equalTo(100L)); - - bucket = buckets.get(1); - assertThat(bucket, notNullValue()); - assertThat((String) bucket.getKey(), equalTo("10.0.0.100-10.0.0.200")); - assertThat(bucket.getFromAsString(), equalTo("10.0.0.100")); - assertThat(((Number) bucket.getFrom()).doubleValue(), equalTo((double) LegacyIpFieldMapper.ipToLong("10.0.0.100"))); - assertThat(bucket.getToAsString(), equalTo("10.0.0.200")); - assertThat(((Number) bucket.getTo()).doubleValue(), equalTo((double) LegacyIpFieldMapper.ipToLong("10.0.0.200"))); - assertThat(bucket.getDocCount(), equalTo(101L)); - - bucket = buckets.get(2); - assertThat(bucket, notNullValue()); - assertThat((String) bucket.getKey(), equalTo("10.0.0.200-*")); - assertThat(bucket.getFromAsString(), equalTo("10.0.0.200")); - assertThat(((Number) bucket.getFrom()).doubleValue(), equalTo((double) LegacyIpFieldMapper.ipToLong("10.0.0.200"))); - assertThat(((Number) bucket.getTo()).doubleValue(), equalTo(Double.POSITIVE_INFINITY)); - assertThat(bucket.getToAsString(), nullValue()); - assertThat(bucket.getDocCount(), equalTo(56L)); - } - - public void testMultiValuedFieldWithValueScript() throws Exception { - SearchResponse response = client() - .prepareSearch("idx") - .addAggregation( - ipRange("range").field("ips").script(new Script("_value")).addUnboundedTo("10.0.0.100") - .addRange("10.0.0.100", "10.0.0.200").addUnboundedFrom("10.0.0.200")).execute().actionGet(); - - assertSearchResponse(response); - - - Range range = response.getAggregations().get("range"); - assertThat(range, notNullValue()); - assertThat(range.getName(), equalTo("range")); - List buckets = range.getBuckets(); - assertThat(range.getBuckets().size(), equalTo(3)); - - Range.Bucket bucket = buckets.get(0); - assertThat(bucket, notNullValue()); - assertThat((String) bucket.getKey(), equalTo("*-10.0.0.100")); - assertThat(((Number) bucket.getFrom()).doubleValue(), equalTo(Double.NEGATIVE_INFINITY)); - assertThat(bucket.getFromAsString(), nullValue()); - assertThat(bucket.getToAsString(), equalTo("10.0.0.100")); - assertThat(((Number) bucket.getTo()).doubleValue(), equalTo((double) LegacyIpFieldMapper.ipToLong("10.0.0.100"))); - assertThat(bucket.getDocCount(), equalTo(100L)); - - bucket = buckets.get(1); - assertThat(bucket, notNullValue()); - assertThat((String) bucket.getKey(), equalTo("10.0.0.100-10.0.0.200")); - assertThat(bucket.getFromAsString(), equalTo("10.0.0.100")); - assertThat(((Number) bucket.getFrom()).doubleValue(), equalTo((double) LegacyIpFieldMapper.ipToLong("10.0.0.100"))); - assertThat(bucket.getToAsString(), equalTo("10.0.0.200")); - assertThat(((Number) bucket.getTo()).doubleValue(), equalTo((double) LegacyIpFieldMapper.ipToLong("10.0.0.200"))); - assertThat(bucket.getDocCount(), equalTo(101L)); - - bucket = buckets.get(2); - assertThat(bucket, notNullValue()); - assertThat((String) bucket.getKey(), equalTo("10.0.0.200-*")); - assertThat(bucket.getFromAsString(), equalTo("10.0.0.200")); - assertThat(((Number) bucket.getFrom()).doubleValue(), equalTo((double) LegacyIpFieldMapper.ipToLong("10.0.0.200"))); - assertThat(((Number) bucket.getTo()).doubleValue(), equalTo(Double.POSITIVE_INFINITY)); - assertThat(bucket.getToAsString(), nullValue()); - assertThat(bucket.getDocCount(), equalTo(56L)); - } - - public void testScriptSingleValue() throws Exception { - SearchResponse response = client() - .prepareSearch("idx") - .addAggregation( - ipRange("range").script(new Script("doc['ip'].value")).addUnboundedTo("10.0.0.100") - .addRange("10.0.0.100", "10.0.0.200").addUnboundedFrom("10.0.0.200")).execute().actionGet(); - - assertSearchResponse(response); - - - Range range = response.getAggregations().get("range"); - assertThat(range, notNullValue()); - assertThat(range.getName(), equalTo("range")); - List buckets = range.getBuckets(); - assertThat(range.getBuckets().size(), equalTo(3)); - - Range.Bucket bucket = buckets.get(0); - assertThat(bucket, notNullValue()); - assertThat((String) bucket.getKey(), equalTo("*-10.0.0.100")); - assertThat(((Number) bucket.getFrom()).doubleValue(), equalTo(Double.NEGATIVE_INFINITY)); - assertThat(bucket.getFromAsString(), nullValue()); - assertThat(bucket.getToAsString(), equalTo("10.0.0.100")); - assertThat(((Number) bucket.getTo()).doubleValue(), equalTo((double) LegacyIpFieldMapper.ipToLong("10.0.0.100"))); - assertThat(bucket.getDocCount(), equalTo(100L)); - - bucket = buckets.get(1); - assertThat(bucket, notNullValue()); - assertThat((String) bucket.getKey(), equalTo("10.0.0.100-10.0.0.200")); - assertThat(bucket.getFromAsString(), equalTo("10.0.0.100")); - assertThat(((Number) bucket.getFrom()).doubleValue(), equalTo((double) LegacyIpFieldMapper.ipToLong("10.0.0.100"))); - assertThat(bucket.getToAsString(), equalTo("10.0.0.200")); - assertThat(((Number) bucket.getTo()).doubleValue(), equalTo((double) LegacyIpFieldMapper.ipToLong("10.0.0.200"))); - assertThat(bucket.getDocCount(), equalTo(100L)); - - bucket = buckets.get(2); - assertThat(bucket, notNullValue()); - assertThat((String) bucket.getKey(), equalTo("10.0.0.200-*")); - assertThat(bucket.getFromAsString(), equalTo("10.0.0.200")); - assertThat(((Number) bucket.getFrom()).doubleValue(), equalTo((double) LegacyIpFieldMapper.ipToLong("10.0.0.200"))); - assertThat(((Number) bucket.getTo()).doubleValue(), equalTo(Double.POSITIVE_INFINITY)); - assertThat(bucket.getToAsString(), nullValue()); - assertThat(bucket.getDocCount(), equalTo(55L)); - } - - public void testScriptMultiValued() throws Exception { - SearchResponse response = client() - .prepareSearch("idx") - .addAggregation( - ipRange("range").script(new Script("doc['ips'].values")).addUnboundedTo("10.0.0.100") - .addRange("10.0.0.100", "10.0.0.200").addUnboundedFrom("10.0.0.200")).execute().actionGet(); - - assertSearchResponse(response); - - - Range range = response.getAggregations().get("range"); - assertThat(range, notNullValue()); - assertThat(range.getName(), equalTo("range")); - List buckets = range.getBuckets(); - assertThat(range.getBuckets().size(), equalTo(3)); - - Range.Bucket bucket = buckets.get(0); - assertThat(bucket, notNullValue()); - assertThat((String) bucket.getKey(), equalTo("*-10.0.0.100")); - assertThat(((Number) bucket.getFrom()).doubleValue(), equalTo(Double.NEGATIVE_INFINITY)); - assertThat(bucket.getFromAsString(), nullValue()); - assertThat(bucket.getToAsString(), equalTo("10.0.0.100")); - assertThat(((Number) bucket.getTo()).doubleValue(), equalTo((double) LegacyIpFieldMapper.ipToLong("10.0.0.100"))); - assertThat(bucket.getDocCount(), equalTo(100L)); - - bucket = buckets.get(1); - assertThat(bucket, notNullValue()); - assertThat((String) bucket.getKey(), equalTo("10.0.0.100-10.0.0.200")); - assertThat(bucket.getFromAsString(), equalTo("10.0.0.100")); - assertThat(((Number) bucket.getFrom()).doubleValue(), equalTo((double) LegacyIpFieldMapper.ipToLong("10.0.0.100"))); - assertThat(bucket.getToAsString(), equalTo("10.0.0.200")); - assertThat(((Number) bucket.getTo()).doubleValue(), equalTo((double) LegacyIpFieldMapper.ipToLong("10.0.0.200"))); - assertThat(bucket.getDocCount(), equalTo(101L)); - - bucket = buckets.get(2); - assertThat(bucket, notNullValue()); - assertThat((String) bucket.getKey(), equalTo("10.0.0.200-*")); - assertThat(bucket.getFromAsString(), equalTo("10.0.0.200")); - assertThat(((Number) bucket.getFrom()).doubleValue(), equalTo((double) LegacyIpFieldMapper.ipToLong("10.0.0.200"))); - assertThat(((Number) bucket.getTo()).doubleValue(), equalTo(Double.POSITIVE_INFINITY)); - assertThat(bucket.getToAsString(), nullValue()); - assertThat(bucket.getDocCount(), equalTo(56L)); - } - - public void testUnmapped() throws Exception { - SearchResponse response = client().prepareSearch("idx_unmapped") - .addAggregation(ipRange("range") - .field("ip") - .addUnboundedTo("10.0.0.100") - .addRange("10.0.0.100", "10.0.0.200") - .addUnboundedFrom("10.0.0.200")) - .execute().actionGet(); - - assertSearchResponse(response); - - - Range range = response.getAggregations().get("range"); - assertThat(range, notNullValue()); - assertThat(range.getName(), equalTo("range")); - List buckets = range.getBuckets(); - assertThat(range.getBuckets().size(), equalTo(3)); - - Range.Bucket bucket = buckets.get(0); - assertThat(bucket, notNullValue()); - assertThat((String) bucket.getKey(), equalTo("*-10.0.0.100")); - assertThat(((Number) bucket.getFrom()).doubleValue(), equalTo(Double.NEGATIVE_INFINITY)); - assertThat(bucket.getFromAsString(), nullValue()); - assertThat(bucket.getToAsString(), equalTo("10.0.0.100")); - assertThat(((Number) bucket.getTo()).doubleValue(), equalTo((double) LegacyIpFieldMapper.ipToLong("10.0.0.100"))); - assertThat(bucket.getDocCount(), equalTo(0L)); - - bucket = buckets.get(1); - assertThat(bucket, notNullValue()); - assertThat((String) bucket.getKey(), equalTo("10.0.0.100-10.0.0.200")); - assertThat(bucket.getFromAsString(), equalTo("10.0.0.100")); - assertThat(((Number) bucket.getFrom()).doubleValue(), equalTo((double) LegacyIpFieldMapper.ipToLong("10.0.0.100"))); - assertThat(bucket.getToAsString(), equalTo("10.0.0.200")); - assertThat(((Number) bucket.getTo()).doubleValue(), equalTo((double) LegacyIpFieldMapper.ipToLong("10.0.0.200"))); - assertThat(bucket.getDocCount(), equalTo(0L)); - - bucket = buckets.get(2); - assertThat(bucket, notNullValue()); - assertThat((String) bucket.getKey(), equalTo("10.0.0.200-*")); - assertThat(bucket.getFromAsString(), equalTo("10.0.0.200")); - assertThat(((Number) bucket.getFrom()).doubleValue(), equalTo((double) LegacyIpFieldMapper.ipToLong("10.0.0.200"))); - assertThat(((Number) bucket.getTo()).doubleValue(), equalTo(Double.POSITIVE_INFINITY)); - assertThat(bucket.getToAsString(), nullValue()); - assertThat(bucket.getDocCount(), equalTo(0L)); - } - - public void testPartiallyUnmapped() throws Exception { - SearchResponse response = client().prepareSearch("idx", "idx_unmapped") - .addAggregation(ipRange("range") - .field("ip") - .addUnboundedTo("10.0.0.100") - .addRange("10.0.0.100", "10.0.0.200") - .addUnboundedFrom("10.0.0.200")) - .execute().actionGet(); - - assertSearchResponse(response); - - - Range range = response.getAggregations().get("range"); - assertThat(range, notNullValue()); - assertThat(range.getName(), equalTo("range")); - List buckets = range.getBuckets(); - assertThat(range.getBuckets().size(), equalTo(3)); - - Range.Bucket bucket = buckets.get(0); - assertThat(bucket, notNullValue()); - assertThat((String) bucket.getKey(), equalTo("*-10.0.0.100")); - assertThat(((Number) bucket.getFrom()).doubleValue(), equalTo(Double.NEGATIVE_INFINITY)); - assertThat(bucket.getFromAsString(), nullValue()); - assertThat(bucket.getToAsString(), equalTo("10.0.0.100")); - assertThat(((Number) bucket.getTo()).doubleValue(), equalTo((double) LegacyIpFieldMapper.ipToLong("10.0.0.100"))); - assertThat(bucket.getDocCount(), equalTo(100L)); - - bucket = buckets.get(1); - assertThat(bucket, notNullValue()); - assertThat((String) bucket.getKey(), equalTo("10.0.0.100-10.0.0.200")); - assertThat(bucket.getFromAsString(), equalTo("10.0.0.100")); - assertThat(((Number) bucket.getFrom()).doubleValue(), equalTo((double) LegacyIpFieldMapper.ipToLong("10.0.0.100"))); - assertThat(bucket.getToAsString(), equalTo("10.0.0.200")); - assertThat(((Number) bucket.getTo()).doubleValue(), equalTo((double) LegacyIpFieldMapper.ipToLong("10.0.0.200"))); - assertThat(bucket.getDocCount(), equalTo(100L)); - - bucket = buckets.get(2); - assertThat(bucket, notNullValue()); - assertThat((String) bucket.getKey(), equalTo("10.0.0.200-*")); - assertThat(bucket.getFromAsString(), equalTo("10.0.0.200")); - assertThat(((Number) bucket.getFrom()).doubleValue(), equalTo((double) LegacyIpFieldMapper.ipToLong("10.0.0.200"))); - assertThat(((Number) bucket.getTo()).doubleValue(), equalTo(Double.POSITIVE_INFINITY)); - assertThat(bucket.getToAsString(), nullValue()); - assertThat(bucket.getDocCount(), equalTo(55L)); - } - - public void testEmptyAggregation() throws Exception { - SearchResponse searchResponse = client().prepareSearch("empty_bucket_idx") - .setQuery(matchAllQuery()) - .addAggregation(histogram("histo").field("value").interval(1L).minDocCount(0) - .subAggregation(ipRange("ip_range").field("ip").addRange("r1", "10.0.0.1", "10.0.0.10"))) - .execute().actionGet(); - - assertThat(searchResponse.getHits().getTotalHits(), equalTo(2L)); - Histogram histo = searchResponse.getAggregations().get("histo"); - assertThat(histo, Matchers.notNullValue()); - Histogram.Bucket bucket = histo.getBuckets().get(1); - assertThat(bucket, Matchers.notNullValue()); - - Range range = bucket.getAggregations().get("ip_range"); - // TODO: use diamond once JI-9019884 is fixed - List buckets = new ArrayList(range.getBuckets()); - assertThat(range, Matchers.notNullValue()); - assertThat(range.getName(), equalTo("ip_range")); - assertThat(buckets.size(), is(1)); - assertThat((String) buckets.get(0).getKey(), equalTo("r1")); - assertThat(buckets.get(0).getFromAsString(), equalTo("10.0.0.1")); - assertThat(buckets.get(0).getToAsString(), equalTo("10.0.0.10")); - assertThat(buckets.get(0).getDocCount(), equalTo(0L)); - } - - public void testMask0() { - SearchResponse response = client().prepareSearch("idx") - .addAggregation(ipRange("range") - .field("ip") - .addMaskRange("0.0.0.0/0")) - .execute().actionGet(); - - assertSearchResponse(response); - - Range range = response.getAggregations().get("range"); - assertThat(range, notNullValue()); - assertThat(range.getName(), equalTo("range")); - List buckets = range.getBuckets(); - assertThat(range.getBuckets().size(), equalTo(1)); - - Range.Bucket bucket = buckets.get(0); - assertThat((String) bucket.getKey(), equalTo("0.0.0.0/0")); - assertThat(bucket.getFromAsString(), nullValue()); - assertThat(bucket.getToAsString(), nullValue()); - assertThat(((Number) bucket.getTo()).doubleValue(), equalTo(Double.POSITIVE_INFINITY)); - assertEquals(255L, bucket.getDocCount()); - } - - public void testMask0SpecialIps() { - SearchResponse response = client().prepareSearch("range_idx") - .addAggregation(ipRange("range") - .field("ip") - .addMaskRange("0.0.0.0/0")) - .execute().actionGet(); - - assertSearchResponse(response); - - Range range = response.getAggregations().get("range"); - - assertThat(range, notNullValue()); - assertThat(range.getName(), equalTo("range")); - List buckets = range.getBuckets(); - assertThat(range.getBuckets().size(), equalTo(1)); - - Range.Bucket bucket = buckets.get(0); - assertEquals(4L, bucket.getDocCount()); - } -}