From 0c4eb0a02940139ca26ccc45523c03ba26c7d75b Mon Sep 17 00:00:00 2001 From: Nicholas Knize Date: Tue, 2 May 2017 09:56:52 -0500 Subject: [PATCH] Add new ip_range field type This commit adds support for indexing and searching a new ip_range field type. Both IPv4 and IPv6 formats are supported. Tests are updated and docs are added. --- .../index/mapper/RangeFieldMapper.java | 236 ++++++++++-------- .../index/mapper/RangeFieldMapperTests.java | 44 +++- .../index/mapper/RangeFieldTypeTests.java | 28 ++- docs/reference/mapping/types/range.asciidoc | 2 + .../release-notes/6.0.0-alpha1.asciidoc | 1 + 5 files changed, 201 insertions(+), 110 deletions(-) diff --git a/core/src/main/java/org/elasticsearch/index/mapper/RangeFieldMapper.java b/core/src/main/java/org/elasticsearch/index/mapper/RangeFieldMapper.java index 9a271916ac1..257792b8b09 100644 --- a/core/src/main/java/org/elasticsearch/index/mapper/RangeFieldMapper.java +++ b/core/src/main/java/org/elasticsearch/index/mapper/RangeFieldMapper.java @@ -22,6 +22,8 @@ import org.apache.lucene.document.Field; import org.apache.lucene.document.DoubleRange; import org.apache.lucene.document.FloatRange; import org.apache.lucene.document.IntRange; +import org.apache.lucene.document.InetAddressPoint; +import org.apache.lucene.document.InetAddressRange; import org.apache.lucene.document.LongRange; import org.apache.lucene.document.StoredField; import org.apache.lucene.index.IndexOptions; @@ -29,12 +31,12 @@ import org.apache.lucene.index.IndexableField; import org.apache.lucene.search.BoostQuery; import org.apache.lucene.search.Query; import org.apache.lucene.util.BytesRef; -import org.apache.lucene.util.NumericUtils; import org.elasticsearch.common.Explicit; import org.elasticsearch.common.Nullable; import org.elasticsearch.common.geo.ShapeRelation; import org.elasticsearch.common.joda.DateMathParser; import org.elasticsearch.common.joda.FormatDateTimeFormatter; +import org.elasticsearch.common.network.InetAddresses; import org.elasticsearch.common.settings.Setting; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.util.LocaleUtils; @@ -45,6 +47,7 @@ import org.elasticsearch.index.query.QueryShardContext; import org.joda.time.DateTimeZone; import java.io.IOException; +import java.net.InetAddress; import java.util.ArrayList; import java.util.Iterator; import java.util.List; @@ -341,8 +344,8 @@ public class RangeFieldMapper extends FieldMapper { RangeFieldType fieldType = fieldType(); RangeType rangeType = fieldType.rangeType; String fieldName = null; - Number from = rangeType.minValue(); - Number to = rangeType.maxValue(); + Object from = rangeType.minValue(); + Object to = rangeType.maxValue(); boolean includeFrom = DEFAULT_INCLUDE_LOWER; boolean includeTo = DEFAULT_INCLUDE_UPPER; XContentParser.Token token; @@ -427,10 +430,72 @@ public class RangeFieldMapper extends FieldMapper { /** Enum defining the type of range */ public enum RangeType { + IP("ip_range") { + @Override + public Field getRangeField(String name, Range r) { + return new InetAddressRange(name, (InetAddress)r.from, (InetAddress)r.to); + } + @Override + public InetAddress parseFrom(RangeFieldType fieldType, XContentParser parser, boolean coerce, boolean included) + throws IOException { + InetAddress address = InetAddresses.forString(parser.text()); + return included ? address : nextUp(address); + } + @Override + public InetAddress parseTo(RangeFieldType fieldType, XContentParser parser, boolean coerce, boolean included) + throws IOException { + InetAddress address = InetAddresses.forString(parser.text()); + return included ? address : nextDown(address); + } + @Override + public InetAddress parse(Object value, boolean coerce) { + return value instanceof InetAddress ? (InetAddress) value : InetAddresses.forString((String) value); + } + @Override + public InetAddress minValue() { + return InetAddressPoint.MIN_VALUE; + } + @Override + public InetAddress maxValue() { + return InetAddressPoint.MAX_VALUE; + } + @Override + public InetAddress nextUp(Object value) { + return InetAddressPoint.nextUp((InetAddress)value); + } + @Override + public InetAddress nextDown(Object value) { + return InetAddressPoint.nextDown((InetAddress)value); + } + @Override + public Query withinQuery(String field, Object from, Object to, boolean includeLower, boolean includeUpper) { + InetAddress lower = (InetAddress)from; + InetAddress upper = (InetAddress)to; + return InetAddressRange.newWithinQuery(field, + includeLower ? lower : nextUp(lower), includeUpper ? upper : nextDown(upper)); + } + @Override + public Query containsQuery(String field, Object from, Object to, boolean includeLower, boolean includeUpper) { + InetAddress lower = (InetAddress)from; + InetAddress upper = (InetAddress)to; + return InetAddressRange.newContainsQuery(field, + includeLower ? lower : nextUp(lower), includeUpper ? upper : nextDown(upper)); + } + @Override + public Query intersectsQuery(String field, Object from, Object to, boolean includeLower, boolean includeUpper) { + InetAddress lower = (InetAddress)from; + InetAddress upper = (InetAddress)to; + return InetAddressRange.newIntersectsQuery(field, + includeLower ? lower : nextUp(lower), includeUpper ? upper : nextDown(upper)); + } + public String toString(InetAddress address) { + return InetAddresses.toAddrString(address); + } + }, DATE("date_range", NumberType.LONG) { @Override public Field getRangeField(String name, Range r) { - return new LongRange(name, new long[] {r.from.longValue()}, new long[] {r.to.longValue()}); + return new LongRange(name, new long[] {((Number)r.from).longValue()}, new long[] {((Number)r.to).longValue()}); } private Number parse(DateMathParser dateMathParser, String dateStr) { return dateMathParser.parse(dateStr, () -> {throw new IllegalArgumentException("now is not used at indexing time");}); @@ -456,16 +521,12 @@ public class RangeFieldMapper extends FieldMapper { return Long.MAX_VALUE; } @Override - public Number nextUp(Number value) { - return LONG.nextUp(value); + public Long nextUp(Object value) { + return (long) LONG.nextUp(value); } @Override - public Number nextDown(Number value) { - return LONG.nextDown(value); - } - @Override - public byte[] getBytes(Range r) { - return LONG.getBytes(r); + public Long nextDown(Object value) { + return (long) LONG.nextDown(value); } @Override public Query rangeQuery(String field, Object lowerTerm, Object upperTerm, boolean includeLower, boolean includeUpper, @@ -484,15 +545,15 @@ public class RangeFieldMapper extends FieldMapper { return super.rangeQuery(field, low, high, includeLower, includeUpper, relation, zone, dateMathParser, context); } @Override - public Query withinQuery(String field, Number from, Number to, boolean includeLower, boolean includeUpper) { + public Query withinQuery(String field, Object from, Object to, boolean includeLower, boolean includeUpper) { return LONG.withinQuery(field, from, to, includeLower, includeUpper); } @Override - public Query containsQuery(String field, Number from, Number to, boolean includeLower, boolean includeUpper) { + public Query containsQuery(String field, Object from, Object to, boolean includeLower, boolean includeUpper) { return LONG.containsQuery(field, from, to, includeLower, includeUpper); } @Override - public Query intersectsQuery(String field, Number from, Number to, boolean includeLower, boolean includeUpper) { + public Query intersectsQuery(String field, Object from, Object to, boolean includeLower, boolean includeUpper) { return LONG.intersectsQuery(field, from, to, includeLower, includeUpper); } }, @@ -507,38 +568,31 @@ public class RangeFieldMapper extends FieldMapper { return Float.POSITIVE_INFINITY; } @Override - public Float nextUp(Number value) { - return Math.nextUp(value.floatValue()); + public Float nextUp(Object value) { + return Math.nextUp(((Number)value).floatValue()); } @Override - public Float nextDown(Number value) { - return Math.nextDown(value.floatValue()); + public Float nextDown(Object value) { + return Math.nextDown(((Number)value).floatValue()); } @Override public Field getRangeField(String name, Range r) { - return new FloatRange(name, new float[] {r.from.floatValue()}, new float[] {r.to.floatValue()}); + return new FloatRange(name, new float[] {((Number)r.from).floatValue()}, new float[] {((Number)r.to).floatValue()}); } @Override - public byte[] getBytes(Range r) { - byte[] b = new byte[Float.BYTES*2]; - NumericUtils.intToSortableBytes(NumericUtils.floatToSortableInt(r.from.floatValue()), b, 0); - NumericUtils.intToSortableBytes(NumericUtils.floatToSortableInt(r.to.floatValue()), b, Float.BYTES); - return b; - } - @Override - public Query withinQuery(String field, Number from, Number to, boolean includeFrom, boolean includeTo) { + public Query withinQuery(String field, Object from, Object to, boolean includeFrom, boolean includeTo) { return FloatRange.newWithinQuery(field, new float[] {includeFrom ? (Float)from : Math.nextUp((Float)from)}, new float[] {includeTo ? (Float)to : Math.nextDown((Float)to)}); } @Override - public Query containsQuery(String field, Number from, Number to, boolean includeFrom, boolean includeTo) { + public Query containsQuery(String field, Object from, Object to, boolean includeFrom, boolean includeTo) { return FloatRange.newContainsQuery(field, new float[] {includeFrom ? (Float)from : Math.nextUp((Float)from)}, new float[] {includeTo ? (Float)to : Math.nextDown((Float)to)}); } @Override - public Query intersectsQuery(String field, Number from, Number to, boolean includeFrom, boolean includeTo) { + public Query intersectsQuery(String field, Object from, Object to, boolean includeFrom, boolean includeTo) { return FloatRange.newIntersectsQuery(field, new float[] {includeFrom ? (Float)from : Math.nextUp((Float)from)}, new float[] {includeTo ? (Float)to : Math.nextDown((Float)to)}); @@ -554,38 +608,31 @@ public class RangeFieldMapper extends FieldMapper { return Double.POSITIVE_INFINITY; } @Override - public Double nextUp(Number value) { - return Math.nextUp(value.doubleValue()); + public Double nextUp(Object value) { + return Math.nextUp(((Number)value).doubleValue()); } @Override - public Double nextDown(Number value) { - return Math.nextDown(value.doubleValue()); + public Double nextDown(Object value) { + return Math.nextDown(((Number)value).doubleValue()); } @Override public Field getRangeField(String name, Range r) { - return new DoubleRange(name, new double[] {r.from.doubleValue()}, new double[] {r.to.doubleValue()}); + return new DoubleRange(name, new double[] {((Number)r.from).doubleValue()}, new double[] {((Number)r.to).doubleValue()}); } @Override - public byte[] getBytes(Range r) { - byte[] b = new byte[Double.BYTES*2]; - NumericUtils.longToSortableBytes(NumericUtils.doubleToSortableLong(r.from.doubleValue()), b, 0); - NumericUtils.longToSortableBytes(NumericUtils.doubleToSortableLong(r.to.doubleValue()), b, Double.BYTES); - return b; - } - @Override - public Query withinQuery(String field, Number from, Number to, boolean includeFrom, boolean includeTo) { + public Query withinQuery(String field, Object from, Object to, boolean includeFrom, boolean includeTo) { return DoubleRange.newWithinQuery(field, new double[] {includeFrom ? (Double)from : Math.nextUp((Double)from)}, new double[] {includeTo ? (Double)to : Math.nextDown((Double)to)}); } @Override - public Query containsQuery(String field, Number from, Number to, boolean includeFrom, boolean includeTo) { + public Query containsQuery(String field, Object from, Object to, boolean includeFrom, boolean includeTo) { return DoubleRange.newContainsQuery(field, new double[] {includeFrom ? (Double)from : Math.nextUp((Double)from)}, new double[] {includeTo ? (Double)to : Math.nextDown((Double)to)}); } @Override - public Query intersectsQuery(String field, Number from, Number to, boolean includeFrom, boolean includeTo) { + public Query intersectsQuery(String field, Object from, Object to, boolean includeFrom, boolean includeTo) { return DoubleRange.newIntersectsQuery(field, new double[] {includeFrom ? (Double)from : Math.nextUp((Double)from)}, new double[] {includeTo ? (Double)to : Math.nextDown((Double)to)}); @@ -603,36 +650,29 @@ public class RangeFieldMapper extends FieldMapper { return Integer.MAX_VALUE; } @Override - public Integer nextUp(Number value) { - return value.intValue() + 1; + public Integer nextUp(Object value) { + return ((Number)value).intValue() + 1; } @Override - public Integer nextDown(Number value) { - return value.intValue() - 1; + public Integer nextDown(Object value) { + return ((Number)value).intValue() - 1; } @Override public Field getRangeField(String name, Range r) { - return new IntRange(name, new int[] {r.from.intValue()}, new int[] {r.to.intValue()}); + return new IntRange(name, new int[] {((Number)r.from).intValue()}, new int[] {((Number)r.to).intValue()}); } @Override - public byte[] getBytes(Range r) { - byte[] b = new byte[Integer.BYTES*2]; - NumericUtils.intToSortableBytes(r.from.intValue(), b, 0); - NumericUtils.intToSortableBytes(r.to.intValue(), b, Integer.BYTES); - return b; - } - @Override - public Query withinQuery(String field, Number from, Number to, boolean includeFrom, boolean includeTo) { + public Query withinQuery(String field, Object from, Object to, boolean includeFrom, boolean includeTo) { return IntRange.newWithinQuery(field, new int[] {(Integer)from + (includeFrom ? 0 : 1)}, new int[] {(Integer)to - (includeTo ? 0 : 1)}); } @Override - public Query containsQuery(String field, Number from, Number to, boolean includeFrom, boolean includeTo) { + public Query containsQuery(String field, Object from, Object to, boolean includeFrom, boolean includeTo) { return IntRange.newContainsQuery(field, new int[] {(Integer)from + (includeFrom ? 0 : 1)}, new int[] {(Integer)to - (includeTo ? 0 : 1)}); } @Override - public Query intersectsQuery(String field, Number from, Number to, boolean includeFrom, boolean includeTo) { + public Query intersectsQuery(String field, Object from, Object to, boolean includeFrom, boolean includeTo) { return IntRange.newIntersectsQuery(field, new int[] {(Integer)from + (includeFrom ? 0 : 1)}, new int[] {(Integer)to - (includeTo ? 0 : 1)}); } @@ -647,43 +687,40 @@ public class RangeFieldMapper extends FieldMapper { return Long.MAX_VALUE; } @Override - public Long nextUp(Number value) { - return value.longValue() + 1; + public Long nextUp(Object value) { + return ((Number)value).longValue() + 1; } @Override - public Long nextDown(Number value) { - return value.longValue() - 1; + public Long nextDown(Object value) { + return ((Number)value).longValue() - 1; } @Override public Field getRangeField(String name, Range r) { - return new LongRange(name, new long[] {r.from.longValue()}, new long[] {r.to.longValue()}); + return new LongRange(name, new long[] {((Number)r.from).longValue()}, + new long[] {((Number)r.to).longValue()}); } @Override - public byte[] getBytes(Range r) { - byte[] b = new byte[Long.BYTES*2]; - long from = r.from == null ? Long.MIN_VALUE : r.from.longValue(); - long to = r.to == null ? Long.MAX_VALUE : r.to.longValue(); - NumericUtils.longToSortableBytes(from, b, 0); - NumericUtils.longToSortableBytes(to, b, Long.BYTES); - return b; - } - @Override - public Query withinQuery(String field, Number from, Number to, boolean includeFrom, boolean includeTo) { + public Query withinQuery(String field, Object from, Object to, boolean includeFrom, boolean includeTo) { return LongRange.newWithinQuery(field, new long[] {(Long)from + (includeFrom ? 0 : 1)}, new long[] {(Long)to - (includeTo ? 0 : 1)}); } @Override - public Query containsQuery(String field, Number from, Number to, boolean includeFrom, boolean includeTo) { + public Query containsQuery(String field, Object from, Object to, boolean includeFrom, boolean includeTo) { return LongRange.newContainsQuery(field, new long[] {(Long)from + (includeFrom ? 0 : 1)}, new long[] {(Long)to - (includeTo ? 0 : 1)}); } @Override - public Query intersectsQuery(String field, Number from, Number to, boolean includeFrom, boolean includeTo) { + public Query intersectsQuery(String field, Object from, Object to, boolean includeFrom, boolean includeTo) { return LongRange.newIntersectsQuery(field, new long[] {(Long)from + (includeFrom ? 0 : 1)}, new long[] {(Long)to - (includeTo ? 0 : 1)}); } }; + RangeType(String name) { + this.name = name; + this.numberType = null; + } + RangeType(String name, NumberType type) { this.name = name; this.numberType = type; @@ -694,7 +731,6 @@ public class RangeFieldMapper extends FieldMapper { return name; } - protected abstract byte[] getBytes(Range range); public abstract Field getRangeField(String name, Range range); public List createFields(String name, Range range, boolean indexed, boolean docValued, boolean stored) { assert range != null : "range cannot be null when creating fields"; @@ -709,29 +745,31 @@ public class RangeFieldMapper extends FieldMapper { return fields; } /** parses from value. rounds according to included flag */ - public Number parseFrom(RangeFieldType fieldType, XContentParser parser, boolean coerce, boolean included) throws IOException { + public Object parseFrom(RangeFieldType fieldType, XContentParser parser, boolean coerce, boolean included) throws IOException { Number value = numberType.parse(parser, coerce); - return included ? value : nextUp(value); + return included ? value : (Number)nextUp(value); } /** parses to value. rounds according to included flag */ - public Number parseTo(RangeFieldType fieldType, XContentParser parser, boolean coerce, boolean included) throws IOException { + public Object parseTo(RangeFieldType fieldType, XContentParser parser, boolean coerce, boolean included) throws IOException { Number value = numberType.parse(parser, coerce); - return included ? value : nextDown(value); + return included ? value : (Number)nextDown(value); } - public abstract Number minValue(); - public abstract Number maxValue(); - public abstract Number nextUp(Number value); - public abstract Number nextDown(Number value); - public abstract Query withinQuery(String field, Number from, Number to, boolean includeFrom, boolean includeTo); - public abstract Query containsQuery(String field, Number from, Number to, boolean includeFrom, boolean includeTo); - public abstract Query intersectsQuery(String field, Number from, Number to, boolean includeFrom, boolean includeTo); - + public abstract Object minValue(); + public abstract Object maxValue(); + public abstract Object nextUp(Object value); + public abstract Object nextDown(Object value); + public abstract Query withinQuery(String field, Object from, Object to, boolean includeFrom, boolean includeTo); + public abstract Query containsQuery(String field, Object from, Object to, boolean includeFrom, boolean includeTo); + public abstract Query intersectsQuery(String field, Object from, Object to, boolean includeFrom, boolean includeTo); + public Object parse(Object value, boolean coerce) { + return numberType.parse(value, coerce); + } public Query rangeQuery(String field, Object from, Object to, boolean includeFrom, boolean includeTo, ShapeRelation relation, @Nullable DateTimeZone timeZone, @Nullable DateMathParser dateMathParser, QueryShardContext context) { - Number lower = from == null ? minValue() : numberType.parse(from, false); - Number upper = to == null ? maxValue() : numberType.parse(to, false); + Object lower = from == null ? minValue() : parse(from, false); + Object upper = to == null ? maxValue() : parse(to, false); if (relation == ShapeRelation.WITHIN) { return withinQuery(field, lower, upper, includeFrom, includeTo); } else if (relation == ShapeRelation.CONTAINS) { @@ -747,12 +785,12 @@ public class RangeFieldMapper extends FieldMapper { /** Class defining a range */ public static class Range { RangeType type; - private Number from; - private Number to; + private Object from; + private Object to; private boolean includeFrom; private boolean includeTo; - public Range(RangeType type, Number from, Number to, boolean includeFrom, boolean includeTo) { + public Range(RangeType type, Object from, Object to, boolean includeFrom, boolean includeTo) { this.type = type; this.from = from; this.to = to; @@ -764,9 +802,11 @@ public class RangeFieldMapper extends FieldMapper { public String toString() { StringBuilder sb = new StringBuilder(); sb.append(includeFrom ? '[' : '('); - sb.append(includeFrom || from.equals(type.minValue()) ? from : type.nextDown(from)); - sb.append(':'); - sb.append(includeTo || to.equals(type.maxValue()) ? to : type.nextUp(to)); + Object f = includeFrom || from.equals(type.minValue()) ? from : type.nextDown(from); + Object t = includeTo || to.equals(type.maxValue()) ? to : type.nextUp(to); + sb.append(type == RangeType.IP ? InetAddresses.toAddrString((InetAddress)f) : f.toString()); + sb.append(" : "); + sb.append(type == RangeType.IP ? InetAddresses.toAddrString((InetAddress)t) : t.toString()); sb.append(includeTo ? ']' : ')'); return sb.toString(); } diff --git a/core/src/test/java/org/elasticsearch/index/mapper/RangeFieldMapperTests.java b/core/src/test/java/org/elasticsearch/index/mapper/RangeFieldMapperTests.java index 18a771bb467..a6fbfc44a56 100644 --- a/core/src/test/java/org/elasticsearch/index/mapper/RangeFieldMapperTests.java +++ b/core/src/test/java/org/elasticsearch/index/mapper/RangeFieldMapperTests.java @@ -18,14 +18,17 @@ */ package org.elasticsearch.index.mapper; +import org.apache.lucene.document.InetAddressPoint; import org.apache.lucene.index.IndexableField; import org.elasticsearch.common.compress.CompressedXContent; +import org.elasticsearch.common.network.InetAddresses; import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentFactory; import org.elasticsearch.common.xcontent.XContentType; import java.io.IOException; +import java.net.InetAddress; import java.util.Arrays; import java.util.HashSet; import java.util.Locale; @@ -40,6 +43,8 @@ import static org.hamcrest.Matchers.containsString; public class RangeFieldMapperTests extends AbstractNumericFieldMapperTestCase { private static String FROM_DATE = "2016-10-31"; private static String TO_DATE = "2016-11-01 20:00:00"; + private static String FROM_IP = "::ffff:c0a8:107"; + private static String TO_IP = "2001:db8::"; private static int FROM = 5; private static String FROM_STR = FROM + ""; private static int TO = 10; @@ -48,12 +53,14 @@ public class RangeFieldMapperTests extends AbstractNumericFieldMapperTestCase { @Override protected void setTypeList() { - TYPES = new HashSet<>(Arrays.asList("date_range", "float_range", "double_range", "integer_range", "long_range")); + TYPES = new HashSet<>(Arrays.asList("date_range", "ip_range", "float_range", "double_range", "integer_range", "long_range")); } private Object getFrom(String type) { if (type.equals("date_range")) { return FROM_DATE; + } else if (type.equals("ip_range")) { + return FROM_IP; } return random().nextBoolean() ? FROM : FROM_STR; } @@ -69,13 +76,17 @@ public class RangeFieldMapperTests extends AbstractNumericFieldMapperTestCase { private Object getTo(String type) { if (type.equals("date_range")) { return TO_DATE; + } else if (type.equals("ip_range")) { + return TO_IP; } return random().nextBoolean() ? TO : TO_STR; } - private Number getMax(String type) { + private Object getMax(String type) { if (type.equals("date_range") || type.equals("long_range")) { return Long.MAX_VALUE; + } else if (type.equals("ip_range")) { + return InetAddressPoint.MAX_VALUE; } else if (type.equals("integer_range")) { return Integer.MAX_VALUE; } else if (type.equals("float_range")) { @@ -189,7 +200,14 @@ public class RangeFieldMapperTests extends AbstractNumericFieldMapperTestCase { assertEquals(2, pointField.fieldType().pointDimensionCount()); IndexableField storedField = fields[1]; assertTrue(storedField.fieldType().stored()); - assertThat(storedField.stringValue(), containsString(type.equals("date_range") ? "1477872000000" : "5")); + String strVal = "5"; + if (type.equals("date_range")) { + strVal = "1477872000000"; + } else if (type.equals("ip_range")) { + strVal = InetAddresses.toAddrString(InetAddresses.forString("192.168.1.7")) + " : " + + InetAddresses.toAddrString(InetAddresses.forString("2001:db8:0:0:0:0:0:0")); + } + assertThat(storedField.stringValue(), containsString(strVal)); } @Override @@ -234,7 +252,8 @@ public class RangeFieldMapperTests extends AbstractNumericFieldMapperTestCase { .endObject().bytes(), XContentType.JSON)); MapperParsingException e = expectThrows(MapperParsingException.class, runnable); - assertThat(e.getCause().getMessage(), anyOf(containsString("passed as String"), containsString("failed to parse date"))); + assertThat(e.getCause().getMessage(), anyOf(containsString("passed as String"), + containsString("failed to parse date"), containsString("is not an IP string literal"))); } @Override @@ -261,7 +280,8 @@ public class RangeFieldMapperTests extends AbstractNumericFieldMapperTestCase { assertEquals(2, doc.rootDoc().getFields("field").length); IndexableField[] fields = doc.rootDoc().getFields("field"); IndexableField storedField = fields[1]; - assertThat(storedField.stringValue(), containsString(type.equals("date_range") ? Long.MAX_VALUE+"" : getMax(type)+"")); + String expected = type.equals("ip_range") ? InetAddresses.toAddrString((InetAddress)getMax(type)) : getMax(type) +""; + assertThat(storedField.stringValue(), containsString(expected)); // test null max value doc = mapper.parse(SourceToParse.source("test", "type", "1", XContentFactory.jsonBuilder() @@ -280,8 +300,14 @@ public class RangeFieldMapperTests extends AbstractNumericFieldMapperTestCase { assertFalse(pointField.fieldType().stored()); storedField = fields[1]; assertTrue(storedField.fieldType().stored()); - assertThat(storedField.stringValue(), containsString(type.equals("date_range") ? "1477872000000" : "5")); - assertThat(storedField.stringValue(), containsString(getMax(type) + "")); + String strVal = "5"; + if (type.equals("date_range")) { + strVal = "1477872000000"; + } else if (type.equals("ip_range")) { + strVal = InetAddresses.toAddrString(InetAddresses.forString("192.168.1.7")) + " : " + + InetAddresses.toAddrString(InetAddresses.forString("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")); + } + assertThat(storedField.stringValue(), containsString(strVal)); } public void testNoBounds() throws Exception { @@ -316,8 +342,8 @@ public class RangeFieldMapperTests extends AbstractNumericFieldMapperTestCase { assertFalse(pointField.fieldType().stored()); IndexableField storedField = fields[1]; assertTrue(storedField.fieldType().stored()); - assertThat(storedField.stringValue(), containsString(type.equals("date_range") ? Long.MAX_VALUE+"" : getMax(type)+"")); - assertThat(storedField.stringValue(), containsString(getMax(type) + "")); + String expected = type.equals("ip_range") ? InetAddresses.toAddrString((InetAddress)getMax(type)) : getMax(type) +""; + assertThat(storedField.stringValue(), containsString(expected)); } public void testIllegalArguments() throws Exception { diff --git a/core/src/test/java/org/elasticsearch/index/mapper/RangeFieldTypeTests.java b/core/src/test/java/org/elasticsearch/index/mapper/RangeFieldTypeTests.java index b3d7db23c38..015509d4a73 100644 --- a/core/src/test/java/org/elasticsearch/index/mapper/RangeFieldTypeTests.java +++ b/core/src/test/java/org/elasticsearch/index/mapper/RangeFieldTypeTests.java @@ -21,6 +21,8 @@ package org.elasticsearch.index.mapper; import com.carrotsearch.randomizedtesting.generators.RandomPicks; import org.apache.lucene.document.DoubleRange; import org.apache.lucene.document.FloatRange; +import org.apache.lucene.document.InetAddressPoint; +import org.apache.lucene.document.InetAddressRange; import org.apache.lucene.document.IntRange; import org.apache.lucene.document.LongRange; import org.apache.lucene.index.IndexOptions; @@ -37,6 +39,7 @@ import org.elasticsearch.test.IndexSettingsModule; import org.joda.time.DateTime; import org.junit.Before; +import java.net.InetAddress; import java.util.Locale; public class RangeFieldTypeTests extends FieldTypeTestCase { @@ -100,6 +103,8 @@ public class RangeFieldTypeTests extends FieldTypeTestCase { return getLongRangeQuery(relation, (long)from, (long)to, includeLower, includeUpper); case DOUBLE: return getDoubleRangeQuery(relation, (double)from, (double)to, includeLower, includeUpper); + case IP: + return getInetAddressRangeQuery(relation, (InetAddress)from, (InetAddress)to, includeLower, includeUpper); default: return getFloatRangeQuery(relation, (float)from, (float)to, includeLower, includeUpper); } @@ -142,7 +147,8 @@ public class RangeFieldTypeTests extends FieldTypeTestCase { return FloatRange.newIntersectsQuery(FIELDNAME, lower, upper); } - private Query getDoubleRangeQuery(ShapeRelation relation, double from, double to, boolean includeLower, boolean includeUpper) { + private Query getDoubleRangeQuery(ShapeRelation relation, double from, double to, boolean includeLower, + boolean includeUpper) { double[] lower = new double[] {includeLower ? from : Math.nextUp(from)}; double[] upper = new double[] {includeUpper ? to : Math.nextDown(to)}; if (relation == ShapeRelation.WITHIN) { @@ -153,7 +159,19 @@ public class RangeFieldTypeTests extends FieldTypeTestCase { return DoubleRange.newIntersectsQuery(FIELDNAME, lower, upper); } - private Object nextFrom() { + private Query getInetAddressRangeQuery(ShapeRelation relation, InetAddress from, InetAddress to, boolean includeLower, + boolean includeUpper) { + InetAddress lower = includeLower ? from : InetAddressPoint.nextUp(from); + InetAddress upper = includeUpper ? to : InetAddressPoint.nextDown(to); + if (relation == ShapeRelation.WITHIN) { + return InetAddressRange.newWithinQuery(FIELDNAME, lower, upper); + } else if (relation == ShapeRelation.CONTAINS) { + return InetAddressRange.newContainsQuery(FIELDNAME, lower, upper); + } + return InetAddressRange.newIntersectsQuery(FIELDNAME, lower, upper); + } + + private Object nextFrom() throws Exception { switch (type) { case INTEGER: return (int)(random().nextInt() * 0.5 - DISTANCE); @@ -163,12 +181,14 @@ public class RangeFieldTypeTests extends FieldTypeTestCase { return (long)(random().nextLong() * 0.5 - DISTANCE); case FLOAT: return (float)(random().nextFloat() * 0.5 - DISTANCE); + case IP: + return InetAddress.getByName("::ffff:c0a8:107"); default: return random().nextDouble() * 0.5 - DISTANCE; } } - private Object nextTo(Object from) { + private Object nextTo(Object from) throws Exception { switch (type) { case INTEGER: return (Integer)from + DISTANCE; @@ -178,6 +198,8 @@ public class RangeFieldTypeTests extends FieldTypeTestCase { return (Long)from + DISTANCE; case DOUBLE: return (Double)from + DISTANCE; + case IP: + return InetAddress.getByName("2001:db8::"); default: return (Float)from + DISTANCE; } diff --git a/docs/reference/mapping/types/range.asciidoc b/docs/reference/mapping/types/range.asciidoc index d4bf76b0cef..4ac7ec03f61 100644 --- a/docs/reference/mapping/types/range.asciidoc +++ b/docs/reference/mapping/types/range.asciidoc @@ -9,6 +9,8 @@ The following range types are supported: `long_range`:: A range of signed 64-bit integers with a minimum value of +-2^63^+ and maximum of +2^63^-1+. `double_range`:: A range of double-precision 64-bit IEEE 754 floating point values. `date_range`:: A range of date values represented as unsigned 64-bit integer milliseconds elapsed since system epoch. +`ip_range` :: A range of ip values supporting either https://en.wikipedia.org/wiki/IPv4[IPv4] or + https://en.wikipedia.org/wiki/IPv6[IPv6] (or mixed) addresses. Below is an example of configuring a mapping with various range fields followed by an example that indexes several range types. diff --git a/docs/reference/release-notes/6.0.0-alpha1.asciidoc b/docs/reference/release-notes/6.0.0-alpha1.asciidoc index d66d3ba0ed1..3af5dc49df8 100644 --- a/docs/reference/release-notes/6.0.0-alpha1.asciidoc +++ b/docs/reference/release-notes/6.0.0-alpha1.asciidoc @@ -147,6 +147,7 @@ Internal:: Core:: * Enable index-time sorting {pull}24055[#24055] (issue: {issue}6720[#6720]) +* Add new ip_range field type {pull}24433[#24433]