diff --git a/core/src/main/java/org/elasticsearch/index/mapper/IpFieldMapper.java b/core/src/main/java/org/elasticsearch/index/mapper/IpFieldMapper.java index df35253b8ec..a15432c635d 100644 --- a/core/src/main/java/org/elasticsearch/index/mapper/IpFieldMapper.java +++ b/core/src/main/java/org/elasticsearch/index/mapper/IpFieldMapper.java @@ -124,7 +124,7 @@ public class IpFieldMapper extends FieldMapper { public static final class IpFieldType extends MappedFieldType { - IpFieldType() { + public IpFieldType() { super(); setTokenized(false); setHasDocValues(true); 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 index 7ec3b3bb9c8..873eef2f715 100644 --- 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 @@ -18,15 +18,6 @@ */ package org.elasticsearch.search.aggregations.bucket.range; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Comparator; -import java.util.List; -import java.util.Map; - -import static java.util.Collections.emptyList; - import org.apache.lucene.index.LeafReaderContext; import org.apache.lucene.index.SortedSetDocValues; import org.apache.lucene.util.BytesRef; @@ -42,6 +33,15 @@ import org.elasticsearch.search.aggregations.pipeline.PipelineAggregator; import org.elasticsearch.search.aggregations.support.ValuesSource; import org.elasticsearch.search.internal.SearchContext; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; +import java.util.Map; + +import static java.util.Collections.emptyList; + /** A range aggregator for values that are stored in SORTED_SET doc values. */ public final class BinaryRangeAggregator extends BucketsAggregator { 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 index ed46062ee06..bb31d9a2480 100644 --- 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 @@ -16,6 +16,7 @@ * specific language governing permissions and limitations * under the License. */ + package org.elasticsearch.search.aggregations.bucket.range; import org.apache.lucene.util.BytesRef; @@ -35,6 +36,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.Objects; import static java.util.Collections.unmodifiableList; @@ -166,6 +168,25 @@ public final class InternalBinaryRange return to == null ? null : format.format(to); } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Bucket bucket = (Bucket) o; + + if (docCount != bucket.docCount) return false; + // keyed and format are ignored since they are already tested on the InternalBinaryRange object + return Objects.equals(key, bucket.key) && + Objects.equals(from, bucket.from) && + Objects.equals(to, bucket.to) && + Objects.equals(aggregations, bucket.aggregations); + } + + @Override + public int hashCode() { + return Objects.hash(getClass(), docCount, key, from, to, aggregations); + } } private final DocValueFormat format; @@ -263,4 +284,17 @@ public final class InternalBinaryRange } return builder; } -} \ No newline at end of file + + @Override + public boolean doEquals(Object obj) { + InternalBinaryRange that = (InternalBinaryRange) obj; + return Objects.equals(buckets, that.buckets) + && Objects.equals(format, that.format) + && Objects.equals(keyed, that.keyed); + } + + @Override + public int doHashCode() { + return Objects.hash(buckets, format, keyed); + } +} diff --git a/core/src/test/java/org/elasticsearch/search/aggregations/bucket/range/InternalBinaryRangeTests.java b/core/src/test/java/org/elasticsearch/search/aggregations/bucket/range/InternalBinaryRangeTests.java new file mode 100644 index 00000000000..a0b23f38d23 --- /dev/null +++ b/core/src/test/java/org/elasticsearch/search/aggregations/bucket/range/InternalBinaryRangeTests.java @@ -0,0 +1,89 @@ +/* + * 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 org.apache.lucene.util.BytesRef; +import org.elasticsearch.common.collect.Tuple; +import org.elasticsearch.common.io.stream.Writeable; +import org.elasticsearch.search.DocValueFormat; +import org.elasticsearch.search.aggregations.InternalAggregationTestCase; +import org.elasticsearch.search.aggregations.InternalAggregations; +import org.elasticsearch.search.aggregations.pipeline.PipelineAggregator; +import org.junit.Before; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class InternalBinaryRangeTests extends InternalAggregationTestCase { + private Tuple[] RANGES; + + @Before + public void randomSortedRanges() { + int numRanges = randomIntBetween(1, 10); + Tuple[] ranges = new Tuple[numRanges]; + for (int i = 0; i < numRanges; i++) { + BytesRef[] values = new BytesRef[2]; + values[0] = new BytesRef(randomAsciiOfLength(15)); + values[1] = new BytesRef(randomAsciiOfLength(15)); + Arrays.sort(values); + ranges[i] = new Tuple(values[0], values[1]); + } + Arrays.sort(ranges, (t1, t2) -> t1.v1().compareTo(t2.v1())); + RANGES = ranges; + } + + + @Override + protected InternalBinaryRange createTestInstance(String name, List pipelineAggregators, + Map metaData) { + boolean keyed = randomBoolean(); + DocValueFormat format = DocValueFormat.RAW; + List buckets = new ArrayList<>(); + for (int i = 0; i < RANGES.length; ++i) { + final int docCount = randomIntBetween(1, 100); + buckets.add(new InternalBinaryRange.Bucket(format, keyed, randomAsciiOfLength(10), + RANGES[i].v1(), RANGES[i].v2(), docCount, InternalAggregations.EMPTY)); + } + return new InternalBinaryRange(name, format, keyed, buckets, pipelineAggregators, Collections.emptyMap()); + } + + @Override + protected Writeable.Reader instanceReader() { + return InternalBinaryRange::new; + } + + @Override + protected void assertReduced(InternalBinaryRange reduced, List inputs) { + int pos = 0; + for (InternalBinaryRange input : inputs) { + assertEquals(reduced.getBuckets().size(), input.getBuckets().size()); + } + for (Range.Bucket bucket : reduced.getBuckets()) { + int expectedCount = 0; + for (InternalBinaryRange input : inputs) { + expectedCount += input.getBuckets().get(pos).getDocCount(); + } + assertEquals(expectedCount, bucket.getDocCount()); + pos ++; + } + } +} diff --git a/core/src/test/java/org/elasticsearch/search/aggregations/bucket/range/IpRangeAggregatorTests.java b/core/src/test/java/org/elasticsearch/search/aggregations/bucket/range/IpRangeAggregatorTests.java new file mode 100644 index 00000000000..f2c76a29b23 --- /dev/null +++ b/core/src/test/java/org/elasticsearch/search/aggregations/bucket/range/IpRangeAggregatorTests.java @@ -0,0 +1,129 @@ +/* + * 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 org.apache.lucene.document.Document; +import org.apache.lucene.document.InetAddressPoint; +import org.apache.lucene.document.SortedSetDocValuesField; +import org.apache.lucene.index.IndexReader; +import org.apache.lucene.index.RandomIndexWriter; +import org.apache.lucene.search.IndexSearcher; +import org.apache.lucene.search.MatchAllDocsQuery; +import org.apache.lucene.store.Directory; +import org.apache.lucene.util.BytesRef; +import org.elasticsearch.common.collect.Tuple; +import org.elasticsearch.common.network.NetworkAddress; +import org.elasticsearch.index.mapper.IpFieldMapper; +import org.elasticsearch.index.mapper.MappedFieldType; +import org.elasticsearch.search.DocValueFormat; +import org.elasticsearch.search.aggregations.AggregatorTestCase; +import org.elasticsearch.search.aggregations.bucket.range.ip.IpRangeAggregationBuilder; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.Arrays; + +public class IpRangeAggregatorTests extends AggregatorTestCase { + + private static InetAddress randomIp(boolean v4, boolean withNull) { + if (withNull && rarely()) { + return null; + } + try { + if (v4) { + byte[] ipv4 = new byte[4]; + random().nextBytes(ipv4); + return InetAddress.getByAddress(ipv4); + } else { + byte[] ipv6 = new byte[16]; + random().nextBytes(ipv6); + return InetAddress.getByAddress(ipv6); + } + } catch (UnknownHostException e) { + throw new AssertionError(); + } + } + + private static boolean isInRange(BytesRef value, BytesRef from, BytesRef to) { + if (to == null || to.compareTo(value) > 0 && (from == null || from.compareTo(value) <= 0)) { + return true; + } + return false; + } + + public void testRanges() throws Exception { + boolean v4 = randomBoolean(); + IpRangeAggregationBuilder builder = new IpRangeAggregationBuilder("test_agg").field("field"); + int numRanges = randomIntBetween(1, 10); + Tuple[] requestedRanges = new Tuple[numRanges]; + for (int i = 0; i < numRanges; i++) { + Tuple[] arr = new Tuple[2]; + for (int j = 0; j < 2; j++) { + InetAddress addr = randomIp(v4, true); + if (addr == null) { + arr[j] = new Tuple(null, null); + } else { + arr[j] = new Tuple(addr, new BytesRef(InetAddressPoint.encode(addr))); + } + } + Arrays.sort(arr, (t1, t2) -> t1.v2().compareTo(t2.v2())); + builder.addRange(NetworkAddress.format(arr[0].v1()), NetworkAddress.format(arr[1].v1())); + requestedRanges[i] = new Tuple(arr[0].v2(), arr[1].v2()); + } + Arrays.sort(requestedRanges, (t1, t2) -> t1.v1().compareTo(t2.v1())); + int[] expectedCounts = new int[numRanges]; + try (Directory dir = newDirectory(); + RandomIndexWriter w = new RandomIndexWriter(random(), dir)) { + int numDocs = randomIntBetween(10, 100); + for (int i = 0; i < numDocs; i++) { + Document doc = new Document(); + int numValues = randomIntBetween(1, 5); + BytesRef[] values = new BytesRef[numValues]; + for (int j = 0; j < numValues; j++) { + values[j] = new BytesRef(InetAddressPoint.encode(randomIp(v4, false))); + doc.add(new SortedSetDocValuesField("field", values[j])); + } + Arrays.sort(values); + for (int j = 0; j < numRanges; j++) { + for (int k = 0; k < numValues; k++) { + if (isInRange(values[k], requestedRanges[j].v1(), requestedRanges[j].v2())) { + expectedCounts[j]++; + break; + } + } + } + w.addDocument(doc); + } + MappedFieldType fieldType = new IpFieldMapper.IpFieldType(); + fieldType.setName("field"); + try (IndexReader reader = w.getReader()) { + IndexSearcher searcher = new IndexSearcher(reader); + InternalBinaryRange range = search(searcher, new MatchAllDocsQuery(), builder, fieldType); + assertEquals(numRanges, range.getBuckets().size()); + for (int i = 0; i < range.getBuckets().size(); i++) { + Tuple expected = requestedRanges[i]; + Range.Bucket bucket = range.getBuckets().get(i); + assertEquals(DocValueFormat.IP.format(expected.v1()), bucket.getFrom()); + assertEquals(DocValueFormat.IP.format(expected.v2()), bucket.getTo()); + assertEquals(expectedCounts[i], bucket.getDocCount()); + } + } + } + } +}