Переглянути джерело

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.
Nicholas Knize 8 роки тому
батько
коміт
0c4eb0a029

+ 138 - 98
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()});
-            }
-            @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;
+                return new IntRange(name, new int[] {((Number)r.from).intValue()}, new int[] {((Number)r.to).intValue()});
             }
             @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()});
-            }
-            @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;
+                return new LongRange(name, new long[] {((Number)r.from).longValue()},
+                    new long[] {((Number)r.to).longValue()});
             }
             @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<IndexableField> 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();
         }

+ 35 - 9
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 {

+ 25 - 3
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;
         }

+ 2 - 0
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.
 

+ 1 - 0
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]