You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by nk...@apache.org on 2016/08/17 20:58:02 UTC

lucene-solr:master: LUCENE-7388: Adds IntRangeField, FloatRangeField, LongRangeField along with supporting queries and tests

Repository: lucene-solr
Updated Branches:
  refs/heads/master fc1adb405 -> 249780cf6


LUCENE-7388: Adds IntRangeField, FloatRangeField, LongRangeField along with supporting queries and tests


Project: http://git-wip-us.apache.org/repos/asf/lucene-solr/repo
Commit: http://git-wip-us.apache.org/repos/asf/lucene-solr/commit/249780cf
Tree: http://git-wip-us.apache.org/repos/asf/lucene-solr/tree/249780cf
Diff: http://git-wip-us.apache.org/repos/asf/lucene-solr/diff/249780cf

Branch: refs/heads/master
Commit: 249780cf6082c21f090a30c1ef1b4f38ece8b448
Parents: fc1adb4
Author: Nicholas Knize <nk...@gmail.com>
Authored: Thu Jul 21 14:32:05 2016 -0500
Committer: Nicholas Knize <nk...@gmail.com>
Committed: Wed Aug 17 15:48:08 2016 -0500

----------------------------------------------------------------------
 lucene/CHANGES.txt                              |   3 +
 .../apache/lucene/document/FloatRangeField.java | 262 +++++++++++++++++++
 .../apache/lucene/document/IntRangeField.java   | 262 +++++++++++++++++++
 .../apache/lucene/document/LongRangeField.java  | 260 ++++++++++++++++++
 .../search/BaseRangeFieldQueryTestCase.java     | 238 ++++++-----------
 .../search/TestDoubleRangeFieldQueries.java     | 154 ++++++++++-
 .../search/TestFloatRangeFieldQueries.java      | 240 +++++++++++++++++
 .../lucene/search/TestIntRangeFieldQueries.java | 240 +++++++++++++++++
 .../search/TestLongRangeFieldQueries.java       | 240 +++++++++++++++++
 9 files changed, 1724 insertions(+), 175 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/249780cf/lucene/CHANGES.txt
----------------------------------------------------------------------
diff --git a/lucene/CHANGES.txt b/lucene/CHANGES.txt
index d3b2db6..3f7f7c3 100644
--- a/lucene/CHANGES.txt
+++ b/lucene/CHANGES.txt
@@ -38,6 +38,9 @@ API Changes
 
 New Features
 
+* LUCENE-7388: Add point based IntRangeField, FloatRangeField, LongRangeField along with
+  supporting queries and tests (Nick Knize)
+
 * LUCENE-7381: Add point based DoubleRangeField and RangeFieldQuery for
   indexing and querying on Ranges up to 4 dimensions (Nick Knize)
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/249780cf/lucene/sandbox/src/java/org/apache/lucene/document/FloatRangeField.java
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/java/org/apache/lucene/document/FloatRangeField.java b/lucene/sandbox/src/java/org/apache/lucene/document/FloatRangeField.java
new file mode 100644
index 0000000..e138ae2
--- /dev/null
+++ b/lucene/sandbox/src/java/org/apache/lucene/document/FloatRangeField.java
@@ -0,0 +1,262 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF 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.apache.lucene.document;
+
+import org.apache.lucene.document.RangeFieldQuery.QueryType;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.util.BytesRef;
+import org.apache.lucene.util.NumericUtils;
+
+/**
+ * An indexed Float Range field.
+ * <p>
+ * This field indexes dimensional ranges defined as min/max pairs. It supports
+ * up to a maximum of 4 dimensions (indexed as 8 numeric values). With 1 dimension representing a single float range,
+ * 2 dimensions representing a bounding box, 3 dimensions a bounding cube, and 4 dimensions a tesseract.
+ * <p>
+ * Multiple values for the same field in one document is supported, and open ended ranges can be defined using
+ * {@code Float.NEGATIVE_INFINITY} and {@code Float.POSITIVE_INFINITY}.
+ *
+ * <p>
+ * This field defines the following static factory methods for common search operations over float ranges:
+ * <ul>
+ *   <li>{@link #newIntersectsQuery newIntersectsQuery()} matches ranges that intersect the defined search range.
+ *   <li>{@link #newWithinQuery newWithinQuery()} matches ranges that are within the defined search range.
+ *   <li>{@link #newContainsQuery newContainsQuery()} matches ranges that contain the defined search range.
+ * </ul>
+ */
+public class FloatRangeField extends Field {
+  /** stores float values so number of bytes is 4 */
+  public static final int BYTES = Float.BYTES;
+
+  /**
+   * Create a new FloatRangeField type, from min/max parallel arrays
+   *
+   * @param name field name. must not be null.
+   * @param min range min values; each entry is the min value for the dimension
+   * @param max range max values; each entry is the max value for the dimension
+   */
+  public FloatRangeField(String name, final float[] min, final float[] max) {
+    super(name, getType(min.length));
+    setRangeValues(min, max);
+  }
+
+  /** set the field type */
+  private static FieldType getType(int dimensions) {
+    if (dimensions > 4) {
+      throw new IllegalArgumentException("FloatRangeField does not support greater than 4 dimensions");
+    }
+
+    FieldType ft = new FieldType();
+    // dimensions is set as 2*dimension size (min/max per dimension)
+    ft.setDimensions(dimensions*2, BYTES);
+    ft.freeze();
+    return ft;
+  }
+
+  /**
+   * Changes the values of the field.
+   * @param min array of min values. (accepts {@code Float.NEGATIVE_INFINITY})
+   * @param max array of max values. (accepts {@code Float.POSITIVE_INFINITY})
+   * @throws IllegalArgumentException if {@code min} or {@code max} is invalid
+   */
+  public void setRangeValues(float[] min, float[] max) {
+    checkArgs(min, max);
+    if (min.length*2 != type.pointDimensionCount() || max.length*2 != type.pointDimensionCount()) {
+      throw new IllegalArgumentException("field (name=" + name + ") uses " + type.pointDimensionCount()/2
+          + " dimensions; cannot change to (incoming) " + min.length + " dimensions");
+    }
+
+    final byte[] bytes;
+    if (fieldsData == null) {
+      bytes = new byte[BYTES*2*min.length];
+      fieldsData = new BytesRef(bytes);
+    } else {
+      bytes = ((BytesRef)fieldsData).bytes;
+    }
+    verifyAndEncode(min, max, bytes);
+  }
+
+  /** validate the arguments */
+  private static void checkArgs(final float[] min, final float[] max) {
+    if (min == null || max == null || min.length == 0 || max.length == 0) {
+      throw new IllegalArgumentException("min/max range values cannot be null or empty");
+    }
+    if (min.length != max.length) {
+      throw new IllegalArgumentException("min/max ranges must agree");
+    }
+    if (min.length > 4) {
+      throw new IllegalArgumentException("FloatRangeField does not support greater than 4 dimensions");
+    }
+  }
+
+  /**
+   * Encodes the min, max ranges into a byte array
+   */
+  private static byte[] encode(float[] min, float[] max) {
+    checkArgs(min, max);
+    byte[] b = new byte[BYTES*2*min.length];
+    verifyAndEncode(min, max, b);
+    return b;
+  }
+
+  /**
+   * encode the ranges into a sortable byte array ({@code Float.NaN} not allowed)
+   * <p>
+   * example for 4 dimensions (8 bytes per dimension value):
+   * minD1 ... minD4 | maxD1 ... maxD4
+   */
+  static void verifyAndEncode(float[] min, float[] max, byte[] bytes) {
+    for (int d=0,i=0,j=min.length*BYTES; d<min.length; ++d, i+=BYTES, j+=BYTES) {
+      if (Double.isNaN(min[d])) {
+        throw new IllegalArgumentException("invalid min value (" + Float.NaN + ")" + " in FloatRangeField");
+      }
+      if (Double.isNaN(max[d])) {
+        throw new IllegalArgumentException("invalid max value (" + Float.NaN + ")" + " in FloatRangeField");
+      }
+      if (min[d] > max[d]) {
+        throw new IllegalArgumentException("min value (" + min[d] + ") is greater than max value (" + max[d] + ")");
+      }
+      encode(min[d], bytes, i);
+      encode(max[d], bytes, j);
+    }
+  }
+
+  /** encode the given value into the byte array at the defined offset */
+  private static void encode(float val, byte[] bytes, int offset) {
+    NumericUtils.intToSortableBytes(NumericUtils.floatToSortableInt(val), bytes, offset);
+  }
+
+  /**
+   * Get the min value for the given dimension
+   * @param dimension the dimension, always positive
+   * @return the decoded min value
+   */
+  public float getMin(int dimension) {
+    if (dimension < 0 || dimension >= type.pointDimensionCount()/2) {
+      throw new IllegalArgumentException("dimension request (" + dimension +
+          ") out of bounds for field (name=" + name + " dimensions=" + type.pointDimensionCount()/2 + "). ");
+    }
+    return decodeMin(((BytesRef)fieldsData).bytes, dimension);
+  }
+
+  /**
+   * Get the max value for the given dimension
+   * @param dimension the dimension, always positive
+   * @return the decoded max value
+   */
+  public float getMax(int dimension) {
+    if (dimension < 0 || dimension >= type.pointDimensionCount()/2) {
+      throw new IllegalArgumentException("dimension request (" + dimension +
+          ") out of bounds for field (name=" + name + " dimensions=" + type.pointDimensionCount()/2 + "). ");
+    }
+    return decodeMax(((BytesRef)fieldsData).bytes, dimension);
+  }
+
+  /** decodes the min value (for the defined dimension) from the encoded input byte array */
+  static float decodeMin(byte[] b, int dimension) {
+    int offset = dimension*BYTES;
+    return NumericUtils.sortableIntToFloat(NumericUtils.sortableBytesToInt(b, offset));
+  }
+
+  /** decodes the max value (for the defined dimension) from the encoded input byte array */
+  static float decodeMax(byte[] b, int dimension) {
+    int offset = b.length/2 + dimension*BYTES;
+    return NumericUtils.sortableIntToFloat(NumericUtils.sortableBytesToInt(b, offset));
+  }
+
+  /**
+   * Create a query for matching indexed ranges that intersect the defined range.
+   * @param field field name. must not be null.
+   * @param min array of min values. (accepts {@code Float.NEGATIVE_INFINITY})
+   * @param max array of max values. (accepts {@code Float.MAX_VALUE})
+   * @return query for matching intersecting ranges (overlap, within, or contains)
+   * @throws IllegalArgumentException if {@code field} is null, {@code min} or {@code max} is invalid
+   */
+  public static Query newIntersectsQuery(String field, final float[] min, final float[] max) {
+    return new RangeFieldQuery(field, encode(min, max), min.length, QueryType.INTERSECTS) {
+      @Override
+      protected String toString(byte[] ranges, int dimension) {
+        return FloatRangeField.toString(ranges, dimension);
+      }
+    };
+  }
+
+  /**
+   * Create a query for matching indexed float ranges that contain the defined range.
+   * @param field field name. must not be null.
+   * @param min array of min values. (accepts {@code Float.NEGATIVE_INFINITY})
+   * @param max array of max values. (accepts {@code Float.POSITIVE_INFINITY})
+   * @return query for matching ranges that contain the defined range
+   * @throws IllegalArgumentException if {@code field} is null, {@code min} or {@code max} is invalid
+   */
+  public static Query newContainsQuery(String field, final float[] min, final float[] max) {
+    return new RangeFieldQuery(field, encode(min, max), min.length, QueryType.CONTAINS) {
+      @Override
+      protected String toString(byte[] ranges, int dimension) {
+        return FloatRangeField.toString(ranges, dimension);
+      }
+    };
+  }
+
+  /**
+   * Create a query for matching indexed ranges that are within the defined range.
+   * @param field field name. must not be null.
+   * @param min array of min values. (accepts {@code Float.NEGATIVE_INFINITY})
+   * @param max array of max values. (accepts {@code Float.POSITIVE_INFINITY})
+   * @return query for matching ranges within the defined range
+   * @throws IllegalArgumentException if {@code field} is null, {@code min} or {@code max} is invalid
+   */
+  public static Query newWithinQuery(String field, final float[] min, final float[] max) {
+    checkArgs(min, max);
+    return new RangeFieldQuery(field, encode(min, max), min.length, QueryType.WITHIN) {
+      @Override
+      protected String toString(byte[] ranges, int dimension) {
+        return FloatRangeField.toString(ranges, dimension);
+      }
+    };
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder sb = new StringBuilder();
+    sb.append(getClass().getSimpleName());
+    sb.append(" <");
+    sb.append(name);
+    sb.append(':');
+    byte[] b = ((BytesRef)fieldsData).bytes;
+    toString(b, 0);
+    for (int d=1; d<type.pointDimensionCount(); ++d) {
+      sb.append(' ');
+      toString(b, d);
+    }
+    sb.append('>');
+
+    return sb.toString();
+  }
+
+  /**
+   * Returns the String representation for the range at the given dimension
+   * @param ranges the encoded ranges, never null
+   * @param dimension the dimension of interest
+   * @return The string representation for the range at the provided dimension
+   */
+  private static String toString(byte[] ranges, int dimension) {
+    return "[" + Float.toString(decodeMin(ranges, dimension)) + " : "
+        + Float.toString(decodeMax(ranges, dimension)) + "]";
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/249780cf/lucene/sandbox/src/java/org/apache/lucene/document/IntRangeField.java
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/java/org/apache/lucene/document/IntRangeField.java b/lucene/sandbox/src/java/org/apache/lucene/document/IntRangeField.java
new file mode 100644
index 0000000..c0ce61d
--- /dev/null
+++ b/lucene/sandbox/src/java/org/apache/lucene/document/IntRangeField.java
@@ -0,0 +1,262 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF 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.apache.lucene.document;
+
+import org.apache.lucene.document.RangeFieldQuery.QueryType;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.util.BytesRef;
+import org.apache.lucene.util.NumericUtils;
+
+/**
+ * An indexed Integer Range field.
+ * <p>
+ * This field indexes dimensional ranges defined as min/max pairs. It supports
+ * up to a maximum of 4 dimensions (indexed as 8 numeric values). With 1 dimension representing a single integer range,
+ * 2 dimensions representing a bounding box, 3 dimensions a bounding cube, and 4 dimensions a tesseract.
+ * <p>
+ * Multiple values for the same field in one document is supported, and open ended ranges can be defined using
+ * {@code Integer.MIN_VALUE} and {@code Integer.MAX_VALUE}.
+ *
+ * <p>
+ * This field defines the following static factory methods for common search operations over integer ranges:
+ * <ul>
+ *   <li>{@link #newIntersectsQuery newIntersectsQuery()} matches ranges that intersect the defined search range.
+ *   <li>{@link #newWithinQuery newWithinQuery()} matches ranges that are within the defined search range.
+ *   <li>{@link #newContainsQuery newContainsQuery()} matches ranges that contain the defined search range.
+ * </ul>
+ */
+public class IntRangeField extends Field {
+  /** stores integer values so number of bytes is 4 */
+  public static final int BYTES = Integer.BYTES;
+
+  /**
+   * Create a new IntRangeField type, from min/max parallel arrays
+   *
+   * @param name field name. must not be null.
+   * @param min range min values; each entry is the min value for the dimension
+   * @param max range max values; each entry is the max value for the dimension
+   */
+  public IntRangeField(String name, final int[] min, final int[] max) {
+    super(name, getType(min.length));
+    setRangeValues(min, max);
+  }
+
+  /** set the field type */
+  private static FieldType getType(int dimensions) {
+    if (dimensions > 4) {
+      throw new IllegalArgumentException("IntRangeField does not support greater than 4 dimensions");
+    }
+
+    FieldType ft = new FieldType();
+    // dimensions is set as 2*dimension size (min/max per dimension)
+    ft.setDimensions(dimensions*2, BYTES);
+    ft.freeze();
+    return ft;
+  }
+
+  /**
+   * Changes the values of the field.
+   * @param min array of min values. (accepts {@code Integer.NEGATIVE_INFINITY})
+   * @param max array of max values. (accepts {@code Integer.POSITIVE_INFINITY})
+   * @throws IllegalArgumentException if {@code min} or {@code max} is invalid
+   */
+  public void setRangeValues(int[] min, int[] max) {
+    checkArgs(min, max);
+    if (min.length*2 != type.pointDimensionCount() || max.length*2 != type.pointDimensionCount()) {
+      throw new IllegalArgumentException("field (name=" + name + ") uses " + type.pointDimensionCount()/2
+          + " dimensions; cannot change to (incoming) " + min.length + " dimensions");
+    }
+
+    final byte[] bytes;
+    if (fieldsData == null) {
+      bytes = new byte[BYTES*2*min.length];
+      fieldsData = new BytesRef(bytes);
+    } else {
+      bytes = ((BytesRef)fieldsData).bytes;
+    }
+    verifyAndEncode(min, max, bytes);
+  }
+
+  /** validate the arguments */
+  private static void checkArgs(final int[] min, final int[] max) {
+    if (min == null || max == null || min.length == 0 || max.length == 0) {
+      throw new IllegalArgumentException("min/max range values cannot be null or empty");
+    }
+    if (min.length != max.length) {
+      throw new IllegalArgumentException("min/max ranges must agree");
+    }
+    if (min.length > 4) {
+      throw new IllegalArgumentException("IntRangeField does not support greater than 4 dimensions");
+    }
+  }
+
+  /**
+   * Encodes the min, max ranges into a byte array
+   */
+  private static byte[] encode(int[] min, int[] max) {
+    checkArgs(min, max);
+    byte[] b = new byte[BYTES*2*min.length];
+    verifyAndEncode(min, max, b);
+    return b;
+  }
+
+  /**
+   * encode the ranges into a sortable byte array ({@code Double.NaN} not allowed)
+   * <p>
+   * example for 4 dimensions (8 bytes per dimension value):
+   * minD1 ... minD4 | maxD1 ... maxD4
+   */
+  static void verifyAndEncode(int[] min, int[] max, byte[] bytes) {
+    for (int d=0,i=0,j=min.length*BYTES; d<min.length; ++d, i+=BYTES, j+=BYTES) {
+      if (Double.isNaN(min[d])) {
+        throw new IllegalArgumentException("invalid min value (" + Double.NaN + ")" + " in IntRangeField");
+      }
+      if (Double.isNaN(max[d])) {
+        throw new IllegalArgumentException("invalid max value (" + Double.NaN + ")" + " in IntRangeField");
+      }
+      if (min[d] > max[d]) {
+        throw new IllegalArgumentException("min value (" + min[d] + ") is greater than max value (" + max[d] + ")");
+      }
+      encode(min[d], bytes, i);
+      encode(max[d], bytes, j);
+    }
+  }
+
+  /** encode the given value into the byte array at the defined offset */
+  private static void encode(int val, byte[] bytes, int offset) {
+    NumericUtils.intToSortableBytes(val, bytes, offset);
+  }
+
+  /**
+   * Get the min value for the given dimension
+   * @param dimension the dimension, always positive
+   * @return the decoded min value
+   */
+  public int getMin(int dimension) {
+    if (dimension < 0 || dimension >= type.pointDimensionCount()/2) {
+      throw new IllegalArgumentException("dimension request (" + dimension +
+          ") out of bounds for field (name=" + name + " dimensions=" + type.pointDimensionCount()/2 + "). ");
+    }
+    return decodeMin(((BytesRef)fieldsData).bytes, dimension);
+  }
+
+  /**
+   * Get the max value for the given dimension
+   * @param dimension the dimension, always positive
+   * @return the decoded max value
+   */
+  public int getMax(int dimension) {
+    if (dimension < 0 || dimension >= type.pointDimensionCount()/2) {
+      throw new IllegalArgumentException("dimension request (" + dimension +
+          ") out of bounds for field (name=" + name + " dimensions=" + type.pointDimensionCount()/2 + "). ");
+    }
+    return decodeMax(((BytesRef)fieldsData).bytes, dimension);
+  }
+
+  /** decodes the min value (for the defined dimension) from the encoded input byte array */
+  static int decodeMin(byte[] b, int dimension) {
+    int offset = dimension*BYTES;
+    return NumericUtils.sortableBytesToInt(b, offset);
+  }
+
+  /** decodes the max value (for the defined dimension) from the encoded input byte array */
+  static int decodeMax(byte[] b, int dimension) {
+    int offset = b.length/2 + dimension*BYTES;
+    return NumericUtils.sortableBytesToInt(b, offset);
+  }
+
+  /**
+   * Create a query for matching indexed ranges that intersect the defined range.
+   * @param field field name. must not be null.
+   * @param min array of min values. (accepts {@code Integer.MIN_VALUE})
+   * @param max array of max values. (accepts {@code Integer.MAX_VALUE})
+   * @return query for matching intersecting ranges (overlap, within, or contains)
+   * @throws IllegalArgumentException if {@code field} is null, {@code min} or {@code max} is invalid
+   */
+  public static Query newIntersectsQuery(String field, final int[] min, final int[] max) {
+    return new RangeFieldQuery(field, encode(min, max), min.length, QueryType.INTERSECTS) {
+      @Override
+      protected String toString(byte[] ranges, int dimension) {
+        return IntRangeField.toString(ranges, dimension);
+      }
+    };
+  }
+
+  /**
+   * Create a query for matching indexed ranges that contain the defined range.
+   * @param field field name. must not be null.
+   * @param min array of min values. (accepts {@code Integer.MIN_VALUE})
+   * @param max array of max values. (accepts {@code Integer.MAX_VALUE})
+   * @return query for matching ranges that contain the defined range
+   * @throws IllegalArgumentException if {@code field} is null, {@code min} or {@code max} is invalid
+   */
+  public static Query newContainsQuery(String field, final int[] min, final int[] max) {
+    return new RangeFieldQuery(field, encode(min, max), min.length, QueryType.CONTAINS) {
+      @Override
+      protected String toString(byte[] ranges, int dimension) {
+        return IntRangeField.toString(ranges, dimension);
+      }
+    };
+  }
+
+  /**
+   * Create a query for matching indexed ranges that are within the defined range.
+   * @param field field name. must not be null.
+   * @param min array of min values. (accepts {@code Integer.MIN_VALUE})
+   * @param max array of max values. (accepts {@code Integer.MAX_VALUE})
+   * @return query for matching ranges within the defined range
+   * @throws IllegalArgumentException if {@code field} is null, {@code min} or {@code max} is invalid
+   */
+  public static Query newWithinQuery(String field, final int[] min, final int[] max) {
+    checkArgs(min, max);
+    return new RangeFieldQuery(field, encode(min, max), min.length, QueryType.WITHIN) {
+      @Override
+      protected String toString(byte[] ranges, int dimension) {
+        return IntRangeField.toString(ranges, dimension);
+      }
+    };
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder sb = new StringBuilder();
+    sb.append(getClass().getSimpleName());
+    sb.append(" <");
+    sb.append(name);
+    sb.append(':');
+    byte[] b = ((BytesRef)fieldsData).bytes;
+    toString(b, 0);
+    for (int d=1; d<type.pointDimensionCount(); ++d) {
+      sb.append(' ');
+      toString(b, d);
+    }
+    sb.append('>');
+
+    return sb.toString();
+  }
+
+  /**
+   * Returns the String representation for the range at the given dimension
+   * @param ranges the encoded ranges, never null
+   * @param dimension the dimension of interest
+   * @return The string representation for the range at the provided dimension
+   */
+  private static String toString(byte[] ranges, int dimension) {
+    return "[" + Integer.toString(decodeMin(ranges, dimension)) + " : "
+        + Integer.toString(decodeMax(ranges, dimension)) + "]";
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/249780cf/lucene/sandbox/src/java/org/apache/lucene/document/LongRangeField.java
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/java/org/apache/lucene/document/LongRangeField.java b/lucene/sandbox/src/java/org/apache/lucene/document/LongRangeField.java
new file mode 100644
index 0000000..b9298b9
--- /dev/null
+++ b/lucene/sandbox/src/java/org/apache/lucene/document/LongRangeField.java
@@ -0,0 +1,260 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF 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.apache.lucene.document;
+
+import org.apache.lucene.document.RangeFieldQuery.QueryType;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.util.BytesRef;
+import org.apache.lucene.util.NumericUtils;
+
+/**
+ * An indexed Long Range field.
+ * <p>
+ * This field indexes dimensional ranges defined as min/max pairs. It supports
+ * up to a maximum of 4 dimensions (indexed as 8 numeric values). With 1 dimension representing a single long range,
+ * 2 dimensions representing a bounding box, 3 dimensions a bounding cube, and 4 dimensions a tesseract.
+ * <p>
+ * Multiple values for the same field in one document is supported, and open ended ranges can be defined using
+ * {@code Long.MIN_VALUE} and {@code Long.MAX_VALUE}.
+ *
+ * <p>
+ * This field defines the following static factory methods for common search operations over long ranges:
+ * <ul>
+ *   <li>{@link #newIntersectsQuery newIntersectsQuery()} matches ranges that intersect the defined search range.
+ *   <li>{@link #newWithinQuery newWithinQuery()} matches ranges that are within the defined search range.
+ *   <li>{@link #newContainsQuery newContainsQuery()} matches ranges that contain the defined search range.
+ * </ul>
+ */
+public class LongRangeField extends Field {
+  /** stores long values so number of bytes is 8 */
+  public static final int BYTES = Long.BYTES;
+
+  /**
+   * Create a new LongRangeField type, from min/max parallel arrays
+   *
+   * @param name field name. must not be null.
+   * @param min range min values; each entry is the min value for the dimension
+   * @param max range max values; each entry is the max value for the dimension
+   */
+  public LongRangeField(String name, final long[] min, final long[] max) {
+    super(name, getType(min.length));
+    setRangeValues(min, max);
+  }
+
+  /** set the field type */
+  private static FieldType getType(int dimensions) {
+    if (dimensions > 4) {
+      throw new IllegalArgumentException("LongRangeField does not support greater than 4 dimensions");
+    }
+
+    FieldType ft = new FieldType();
+    // dimensions is set as 2*dimension size (min/max per dimension)
+    ft.setDimensions(dimensions*2, BYTES);
+    ft.freeze();
+    return ft;
+  }
+
+  /**
+   * Changes the values of the field.
+   * @param min array of min values. (accepts {@code Long.MIN_VALUE})
+   * @param max array of max values. (accepts {@code Long.MAX_VALUE})
+   * @throws IllegalArgumentException if {@code min} or {@code max} is invalid
+   */
+  public void setRangeValues(long[] min, long[] max) {
+    checkArgs(min, max);
+    if (min.length*2 != type.pointDimensionCount() || max.length*2 != type.pointDimensionCount()) {
+      throw new IllegalArgumentException("field (name=" + name + ") uses " + type.pointDimensionCount()/2
+          + " dimensions; cannot change to (incoming) " + min.length + " dimensions");
+    }
+
+    final byte[] bytes;
+    if (fieldsData == null) {
+      bytes = new byte[BYTES*2*min.length];
+      fieldsData = new BytesRef(bytes);
+    } else {
+      bytes = ((BytesRef)fieldsData).bytes;
+    }
+    verifyAndEncode(min, max, bytes);
+  }
+
+  /** validate the arguments */
+  private static void checkArgs(final long[] min, final long[] max) {
+    if (min == null || max == null || min.length == 0 || max.length == 0) {
+      throw new IllegalArgumentException("min/max range values cannot be null or empty");
+    }
+    if (min.length != max.length) {
+      throw new IllegalArgumentException("min/max ranges must agree");
+    }
+    if (min.length > 4) {
+      throw new IllegalArgumentException("LongRangeField does not support greater than 4 dimensions");
+    }
+  }
+
+  /** Encodes the min, max ranges into a byte array */
+  private static byte[] encode(long[] min, long[] max) {
+    checkArgs(min, max);
+    byte[] b = new byte[BYTES*2*min.length];
+    verifyAndEncode(min, max, b);
+    return b;
+  }
+
+  /**
+   * encode the ranges into a sortable byte array ({@code Double.NaN} not allowed)
+   * <p>
+   * example for 4 dimensions (8 bytes per dimension value):
+   * minD1 ... minD4 | maxD1 ... maxD4
+   */
+  static void verifyAndEncode(long[] min, long[] max, byte[] bytes) {
+    for (int d=0,i=0,j=min.length*BYTES; d<min.length; ++d, i+=BYTES, j+=BYTES) {
+      if (Double.isNaN(min[d])) {
+        throw new IllegalArgumentException("invalid min value (" + Double.NaN + ")" + " in IntRangeField");
+      }
+      if (Double.isNaN(max[d])) {
+        throw new IllegalArgumentException("invalid max value (" + Double.NaN + ")" + " in IntRangeField");
+      }
+      if (min[d] > max[d]) {
+        throw new IllegalArgumentException("min value (" + min[d] + ") is greater than max value (" + max[d] + ")");
+      }
+      encode(min[d], bytes, i);
+      encode(max[d], bytes, j);
+    }
+  }
+
+  /** encode the given value into the byte array at the defined offset */
+  private static void encode(long val, byte[] bytes, int offset) {
+    NumericUtils.longToSortableBytes(val, bytes, offset);
+  }
+
+  /**
+   * Get the min value for the given dimension
+   * @param dimension the dimension, always positive
+   * @return the decoded min value
+   */
+  public long getMin(int dimension) {
+    if (dimension < 0 || dimension >= type.pointDimensionCount()/2) {
+      throw new IllegalArgumentException("dimension request (" + dimension +
+          ") out of bounds for field (name=" + name + " dimensions=" + type.pointDimensionCount()/2 + "). ");
+    }
+    return decodeMin(((BytesRef)fieldsData).bytes, dimension);
+  }
+
+  /**
+   * Get the max value for the given dimension
+   * @param dimension the dimension, always positive
+   * @return the decoded max value
+   */
+  public long getMax(int dimension) {
+    if (dimension < 0 || dimension >= type.pointDimensionCount()/2) {
+      throw new IllegalArgumentException("dimension request (" + dimension +
+          ") out of bounds for field (name=" + name + " dimensions=" + type.pointDimensionCount()/2 + "). ");
+    }
+    return decodeMax(((BytesRef)fieldsData).bytes, dimension);
+  }
+
+  /** decodes the min value (for the defined dimension) from the encoded input byte array */
+  static long decodeMin(byte[] b, int dimension) {
+    int offset = dimension*BYTES;
+    return NumericUtils.sortableBytesToLong(b, offset);
+  }
+
+  /** decodes the max value (for the defined dimension) from the encoded input byte array */
+  static long decodeMax(byte[] b, int dimension) {
+    int offset = b.length/2 + dimension*BYTES;
+    return NumericUtils.sortableBytesToLong(b, offset);
+  }
+
+  /**
+   * Create a query for matching indexed ranges that intersect the defined range.
+   * @param field field name. must not be null.
+   * @param min array of min values. (accepts {@code Long.MIN_VALUE})
+   * @param max array of max values. (accepts {@code Long.MAX_VALUE})
+   * @return query for matching intersecting ranges (overlap, within, or contains)
+   * @throws IllegalArgumentException if {@code field} is null, {@code min} or {@code max} is invalid
+   */
+  public static Query newIntersectsQuery(String field, final long[] min, final long[] max) {
+    return new RangeFieldQuery(field, encode(min, max), min.length, QueryType.INTERSECTS) {
+      @Override
+      protected String toString(byte[] ranges, int dimension) {
+        return LongRangeField.toString(ranges, dimension);
+      }
+    };
+  }
+
+  /**
+   * Create a query for matching indexed ranges that contain the defined range.
+   * @param field field name. must not be null.
+   * @param min array of min values. (accepts {@code Long.MIN_VALUE})
+   * @param max array of max values. (accepts {@code Long.MAX_VALUE})
+   * @return query for matching ranges that contain the defined range
+   * @throws IllegalArgumentException if {@code field} is null, {@code min} or {@code max} is invalid
+   */
+  public static Query newContainsQuery(String field, final long[] min, final long[] max) {
+    return new RangeFieldQuery(field, encode(min, max), min.length, QueryType.CONTAINS) {
+      @Override
+      protected String toString(byte[] ranges, int dimension) {
+        return LongRangeField.toString(ranges, dimension);
+      }
+    };
+  }
+
+  /**
+   * Create a query for matching indexed ranges that are within the defined range.
+   * @param field field name. must not be null.
+   * @param min array of min values. (accepts {@code Long.MIN_VALUE})
+   * @param max array of max values. (accepts {@code Long.MAX_VALUE})
+   * @return query for matching ranges within the defined range
+   * @throws IllegalArgumentException if {@code field} is null, {@code min} or {@code max} is invalid
+   */
+  public static Query newWithinQuery(String field, final long[] min, final long[] max) {
+    checkArgs(min, max);
+    return new RangeFieldQuery(field, encode(min, max), min.length, QueryType.WITHIN) {
+      @Override
+      protected String toString(byte[] ranges, int dimension) {
+        return LongRangeField.toString(ranges, dimension);
+      }
+    };
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder sb = new StringBuilder();
+    sb.append(getClass().getSimpleName());
+    sb.append(" <");
+    sb.append(name);
+    sb.append(':');
+    byte[] b = ((BytesRef)fieldsData).bytes;
+    toString(b, 0);
+    for (int d=1; d<type.pointDimensionCount(); ++d) {
+      sb.append(' ');
+      toString(b, d);
+    }
+    sb.append('>');
+
+    return sb.toString();
+  }
+
+  /**
+   * Returns the String representation for the range at the given dimension
+   * @param ranges the encoded ranges, never null
+   * @param dimension the dimension of interest
+   * @return The string representation for the range at the provided dimension
+   */
+  private static String toString(byte[] ranges, int dimension) {
+    return "[" + Long.toString(decodeMin(ranges, dimension)) + " : "
+        + Long.toString(decodeMax(ranges, dimension)) + "]";
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/249780cf/lucene/sandbox/src/test/org/apache/lucene/search/BaseRangeFieldQueryTestCase.java
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/test/org/apache/lucene/search/BaseRangeFieldQueryTestCase.java b/lucene/sandbox/src/test/org/apache/lucene/search/BaseRangeFieldQueryTestCase.java
index d9cb830..9d29330 100644
--- a/lucene/sandbox/src/test/org/apache/lucene/search/BaseRangeFieldQueryTestCase.java
+++ b/lucene/sandbox/src/test/org/apache/lucene/search/BaseRangeFieldQueryTestCase.java
@@ -17,7 +17,6 @@
 package org.apache.lucene.search;
 
 import java.io.IOException;
-import java.util.Arrays;
 import java.util.HashSet;
 import java.util.Set;
 
@@ -41,16 +40,18 @@ import org.apache.lucene.util.IOUtils;
 import org.apache.lucene.util.LuceneTestCase;
 
 /**
- * Abstract class to do basic tests for a RangeField query.
+ * Abstract class to do basic tests for a RangeField query. Testing rigor inspired by {@code BaseGeoPointTestCase}
  */
 public abstract class BaseRangeFieldQueryTestCase extends LuceneTestCase {
-  protected abstract Field newRangeField(double[] min, double[] max);
+  protected abstract Field newRangeField(Range box);
 
-  protected abstract Query newIntersectsQuery(double[] min, double[] max);
+  protected abstract Query newIntersectsQuery(Range box);
 
-  protected abstract Query newContainsQuery(double[] min, double[] max);
+  protected abstract Query newContainsQuery(Range box);
 
-  protected abstract Query newWithinQuery(double[] min, double[] max);
+  protected abstract Query newWithinQuery(Range box);
+
+  protected abstract Range nextRange(int dimensions);
 
   protected int dimension() {
     return random().nextInt(4) + 1;
@@ -82,18 +83,18 @@ public abstract class BaseRangeFieldQueryTestCase extends LuceneTestCase {
       System.out.println("TEST: numDocs=" + numDocs);
     }
 
-    Box[][] boxes = new Box[numDocs][];
+    Range[][] ranges = new Range[numDocs][];
 
     boolean haveRealDoc = true;
 
     nextdoc: for (int id=0; id<numDocs; ++id) {
       int x = random().nextInt(20);
-      if (boxes[id] == null) {
-        boxes[id] = new Box[] {nextBox(dimensions)};
+      if (ranges[id] == null) {
+        ranges[id] = new Range[] {nextRange(dimensions)};
       }
       if (x == 17) {
         // dome docs don't have a box:
-        boxes[id][0].min[0] = Double.NaN;
+        ranges[id][0].isMissing = true;
         if (VERBOSE) {
           System.out.println("  id=" + id + " is missing");
         }
@@ -103,19 +104,19 @@ public abstract class BaseRangeFieldQueryTestCase extends LuceneTestCase {
       if (multiValued == true && random().nextBoolean()) {
         // randomly add multi valued documents (up to 2 fields)
         int n = random().nextInt(2) + 1;
-        boxes[id] = new Box[n];
+        ranges[id] = new Range[n];
         for (int i=0; i<n; ++i) {
-          boxes[id][i] = nextBox(dimensions);
+          ranges[id][i] = nextRange(dimensions);
         }
       }
 
       if (id > 0 && x < 9 && haveRealDoc) {
         int oldID;
         int i=0;
-        // don't step on missing boxes:
+        // don't step on missing ranges:
         while (true) {
           oldID = random().nextInt(id);
-          if (Double.isNaN(boxes[oldID][0].min[0]) == false) {
+          if (ranges[oldID][0].isMissing == false) {
             break;
           } else if (++i > id) {
             continue nextdoc;
@@ -125,11 +126,11 @@ public abstract class BaseRangeFieldQueryTestCase extends LuceneTestCase {
         if (x == dimensions*2) {
           // Fully identical box (use first box in case current is multivalued but old is not)
           for (int d=0; d<dimensions; ++d) {
-            boxes[id][0].min[d] = boxes[oldID][0].min[d];
-            boxes[id][0].max[d] = boxes[oldID][0].max[d];
+            ranges[id][0].setMin(d, ranges[oldID][0].getMin(d));
+            ranges[id][0].setMax(d, ranges[oldID][0].getMax(d));
           }
           if (VERBOSE) {
-            System.out.println("  id=" + id + " box=" + boxes[id] + " (same box as doc=" + oldID + ")");
+            System.out.println("  id=" + id + " box=" + ranges[id] + " (same box as doc=" + oldID + ")");
           }
         } else {
           for (int m = 0, even = dimensions % 2; m < dimensions * 2; ++m) {
@@ -137,14 +138,14 @@ public abstract class BaseRangeFieldQueryTestCase extends LuceneTestCase {
               int d = (int)Math.floor(m/2);
               // current could be multivalue but old may not be, so use first box
               if (even == 0) {
-                boxes[id][0].setVal(d, boxes[oldID][0].min[d]);
+                ranges[id][0].setMin(d, ranges[oldID][0].getMin(d));
                 if (VERBOSE) {
-                  System.out.println("  id=" + id + " box=" + boxes[id] + " (same min[" + d + "] as doc=" + oldID + ")");
+                  System.out.println("  id=" + id + " box=" + ranges[id] + " (same min[" + d + "] as doc=" + oldID + ")");
                 }
               } else {
-                boxes[id][0].setVal(d, boxes[oldID][0].max[d]);
+                ranges[id][0].setMax(d, ranges[oldID][0].getMax(d));
                 if (VERBOSE) {
-                  System.out.println("  id=" + id + " box=" + boxes[id] + " (same max[" + d + "] as doc=" + oldID + ")");
+                  System.out.println("  id=" + id + " box=" + ranges[id] + " (same max[" + d + "] as doc=" + oldID + ")");
                 }
               }
             }
@@ -152,20 +153,20 @@ public abstract class BaseRangeFieldQueryTestCase extends LuceneTestCase {
         }
       }
     }
-    verify(boxes);
+    verify(ranges);
   }
 
-  private void verify(Box[][] boxes) throws Exception {
+  private void verify(Range[][] ranges) throws Exception {
     IndexWriterConfig iwc = newIndexWriterConfig();
     // Else seeds may not reproduce:
     iwc.setMergeScheduler(new SerialMergeScheduler());
     // Else we can get O(N^2) merging
     int mbd = iwc.getMaxBufferedDocs();
-    if (mbd != -1 && mbd < boxes.length/100) {
-      iwc.setMaxBufferedDocs(boxes.length/100);
+    if (mbd != -1 && mbd < ranges.length/100) {
+      iwc.setMaxBufferedDocs(ranges.length/100);
     }
     Directory dir;
-    if (boxes.length > 50000) {
+    if (ranges.length > 50000) {
       dir = newFSDirectory(createTempDir(getClass().getSimpleName()));
     } else {
       dir = newDirectory();
@@ -173,13 +174,13 @@ public abstract class BaseRangeFieldQueryTestCase extends LuceneTestCase {
 
     Set<Integer> deleted = new HashSet<>();
     IndexWriter w = new IndexWriter(dir, iwc);
-    for (int id=0; id < boxes.length; ++id) {
+    for (int id=0; id < ranges.length; ++id) {
       Document doc = new Document();
       doc.add(newStringField("id", ""+id, Field.Store.NO));
       doc.add(new NumericDocValuesField("id", id));
-      if (Double.isNaN(boxes[id][0].min[0]) == false) {
-        for (int n=0; n<boxes[id].length; ++n) {
-          doc.add(newRangeField(boxes[id][n].min, boxes[id][n].max));
+      if (ranges[id][0].isMissing == false) {
+        for (int n=0; n<ranges[id].length; ++n) {
+          doc.add(newRangeField(ranges[id][n]));
         }
       }
       w.addDocument(doc);
@@ -200,7 +201,7 @@ public abstract class BaseRangeFieldQueryTestCase extends LuceneTestCase {
     w.close();
     IndexSearcher s = newSearcher(r);
 
-    int dimensions = boxes[0][0].min.length;
+    int dimensions = ranges[0][0].numDimensions();
     int iters = atLeast(25);
     NumericDocValues docIDToID = MultiDocValues.getNumericValues(r, "id");
     Bits liveDocs = MultiFields.getLiveDocs(s.getIndexReader());
@@ -211,20 +212,20 @@ public abstract class BaseRangeFieldQueryTestCase extends LuceneTestCase {
         System.out.println("\nTEST: iter=" + iter + " s=" + s);
       }
 
-      // occasionally test open ended bounding boxes
-      Box queryBox = nextBox(dimensions);
+      // occasionally test open ended bounding ranges
+      Range queryRange = nextRange(dimensions);
       int rv = random().nextInt(3);
       Query query;
-      Box.QueryType queryType;
+      Range.QueryType queryType;
       if (rv == 0) {
-        queryType = Box.QueryType.INTERSECTS;
-        query = newIntersectsQuery(queryBox.min, queryBox.max);
+        queryType = Range.QueryType.INTERSECTS;
+        query = newIntersectsQuery(queryRange);
       } else if (rv == 1)  {
-        queryType = Box.QueryType.CONTAINS;
-        query = newContainsQuery(queryBox.min, queryBox.max);
+        queryType = Range.QueryType.CONTAINS;
+        query = newContainsQuery(queryRange);
       } else {
-        queryType = Box.QueryType.WITHIN;
-        query = newWithinQuery(queryBox.min, queryBox.max);
+        queryType = Range.QueryType.WITHIN;
+        query = newWithinQuery(queryRange);
       }
 
       if (VERBOSE) {
@@ -255,25 +256,25 @@ public abstract class BaseRangeFieldQueryTestCase extends LuceneTestCase {
         if (liveDocs != null && liveDocs.get(docID) == false) {
           // document is deleted
           expected = false;
-        } else if (Double.isNaN(boxes[id][0].min[0])) {
+        } else if (ranges[id][0].isMissing) {
           expected = false;
         } else {
-          expected = expectedResult(queryBox, boxes[id], queryType);
+          expected = expectedResult(queryRange, ranges[id], queryType);
         }
 
         if (hits.get(docID) != expected) {
           StringBuilder b = new StringBuilder();
           b.append("FAIL (iter " + iter + "): ");
           if (expected == true) {
-            b.append("id=" + id + (boxes[id].length > 1 ? " (MultiValue) " : " ") + "should match but did not\n");
+            b.append("id=" + id + (ranges[id].length > 1 ? " (MultiValue) " : " ") + "should match but did not\n");
           } else {
             b.append("id=" + id + " should not match but did\n");
           }
-          b.append(" queryBox=" + queryBox + "\n");
-          b.append(" box" + ((boxes[id].length > 1) ? "es=" : "=" ) + boxes[id][0]);
-          for (int n=1; n<boxes[id].length; ++n) {
+          b.append(" queryRange=" + queryRange + "\n");
+          b.append(" box" + ((ranges[id].length > 1) ? "es=" : "=" ) + ranges[id][0]);
+          for (int n=1; n<ranges[id].length; ++n) {
             b.append(", ");
-            b.append(boxes[id][n]);
+            b.append(ranges[id][n]);
           }
           b.append("\n queryType=" + queryType + "\n");
           b.append(" deleted?=" + (liveDocs != null && liveDocs.get(docID) == false));
@@ -284,144 +285,51 @@ public abstract class BaseRangeFieldQueryTestCase extends LuceneTestCase {
     IOUtils.close(r, dir);
   }
 
-  protected boolean expectedResult(Box queryBox, Box[] box, Box.QueryType queryType) {
-    for (int i=0; i<box.length; ++i) {
-      if (expectedBBoxQueryResult(queryBox, box[i], queryType) == true) {
+  protected boolean expectedResult(Range queryRange, Range[] range, Range.QueryType queryType) {
+    for (int i=0; i<range.length; ++i) {
+      if (expectedBBoxQueryResult(queryRange, range[i], queryType) == true) {
         return true;
       }
     }
     return false;
   }
 
-  protected boolean expectedBBoxQueryResult(Box queryBox, Box box, Box.QueryType queryType) {
-    if (box.equals(queryBox)) {
+  protected boolean expectedBBoxQueryResult(Range queryRange, Range range, Range.QueryType queryType) {
+    if (queryRange.isEqual(range)) {
       return true;
     }
-    Box.QueryType relation = box.relate(queryBox);
-    if (queryType == Box.QueryType.INTERSECTS) {
+    Range.QueryType relation = range.relate(queryRange);
+    if (queryType == Range.QueryType.INTERSECTS) {
       return relation != null;
     }
     return relation == queryType;
   }
 
-  protected double nextDoubleInternal() {
-    if (rarely()) {
-      return random().nextBoolean() ? Double.POSITIVE_INFINITY : Double.NEGATIVE_INFINITY;
-    }
-    double max = 100 / 2;
-    return (max + max) * random().nextDouble() - max;
-  }
-
-  protected Box nextBox(int dimensions) {
-    double[] min = new double[dimensions];
-    double[] max = new double[dimensions];
-
-    for (int d=0; d<dimensions; ++d) {
-      min[d] = nextDoubleInternal();
-      max[d] = nextDoubleInternal();
-    }
-
-    return new Box(min, max);
-  }
-
-  protected static class Box {
-    double[] min;
-    double[] max;
+  abstract static class Range {
+    protected boolean isMissing = false;
 
     enum QueryType { INTERSECTS, WITHIN, CONTAINS }
 
-    Box(double[] min, double[] max) {
-      assert min != null && max != null && min.length > 0 && max.length > 0
-          : "test box: min/max cannot be null or empty";
-      assert min.length == max.length : "test box: min/max length do not agree";
-      this.min = new double[min.length];
-      this.max = new double[max.length];
-      for (int d=0; d<min.length; ++d) {
-        this.min[d] = Math.min(min[d], max[d]);
-        this.max[d] = Math.max(min[d], max[d]);
-      }
-    }
-
-    protected void setVal(int dimension, double val) {
-      if (val <= min[dimension]) {
-        min[dimension] = val;
-      } else {
-        max[dimension] = val;
-      }
-    }
-
-    @Override
-    public boolean equals(Object o) {
-      return o != null
-          && getClass() == o.getClass()
-          && equalTo(getClass().cast(o));
-    }
-
-    private boolean equalTo(Box o) {
-      return Arrays.equals(min, o.min)
-          && Arrays.equals(max, o.max);
-    }
-
-    @Override
-    public int hashCode() {
-      int result = Arrays.hashCode(min);
-      result = 31 * result + Arrays.hashCode(max);
-      return result;
-    }
-
-    QueryType relate(Box other) {
-      // check disjoint
-      for (int d=0; d<this.min.length; ++d) {
-        if (this.min[d] > other.max[d] || this.max[d] < other.min[d]) {
-          // disjoint:
-          return null;
-        }
-      }
-
-      // check within
-      boolean within = true;
-      for (int d=0; d<this.min.length; ++d) {
-        if ((this.min[d] >= other.min[d] && this.max[d] <= other.max[d]) == false) {
-          // not within:
-          within = false;
-          break;
-        }
-      }
-      if (within == true) {
+    protected abstract int numDimensions();
+    protected abstract Object getMin(int dim);
+    protected abstract void setMin(int dim, Object val);
+    protected abstract Object getMax(int dim);
+    protected abstract void setMax(int dim, Object val);
+    protected abstract boolean isEqual(Range other);
+    protected abstract boolean isDisjoint(Range other);
+    protected abstract boolean isWithin(Range other);
+    protected abstract boolean contains(Range other);
+
+    protected QueryType relate(Range other) {
+      if (isDisjoint(other)) {
+        // if disjoint; return null:
+        return null;
+      } else if (isWithin(other)) {
         return QueryType.WITHIN;
-      }
-
-      // check contains
-      boolean contains = true;
-      for (int d=0; d<this.min.length; ++d) {
-        if ((this.min[d] <= other.min[d] && this.max[d] >= other.max[d]) == false) {
-          // not contains:
-          contains = false;
-          break;
-        }
-      }
-      if (contains == true) {
+      } else if (contains(other)) {
         return QueryType.CONTAINS;
       }
       return QueryType.INTERSECTS;
     }
-
-    @Override
-    public String toString() {
-      StringBuilder b = new StringBuilder();
-      b.append("Box(");
-      b.append(min[0]);
-      b.append(" TO ");
-      b.append(max[0]);
-      for (int d=1; d<min.length; ++d) {
-        b.append(", ");
-        b.append(min[d]);
-        b.append(" TO ");
-        b.append(max[d]);
-      }
-      b.append(")");
-
-      return b.toString();
-    }
   }
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/249780cf/lucene/sandbox/src/test/org/apache/lucene/search/TestDoubleRangeFieldQueries.java
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/test/org/apache/lucene/search/TestDoubleRangeFieldQueries.java b/lucene/sandbox/src/test/org/apache/lucene/search/TestDoubleRangeFieldQueries.java
index 70857a3..26b8b99 100644
--- a/lucene/sandbox/src/test/org/apache/lucene/search/TestDoubleRangeFieldQueries.java
+++ b/lucene/sandbox/src/test/org/apache/lucene/search/TestDoubleRangeFieldQueries.java
@@ -16,6 +16,8 @@
  */
 package org.apache.lucene.search;
 
+import java.util.Arrays;
+
 import org.apache.lucene.document.Document;
 import org.apache.lucene.document.DoubleRangeField;
 import org.apache.lucene.index.IndexReader;
@@ -23,25 +25,50 @@ import org.apache.lucene.index.RandomIndexWriter;
 import org.apache.lucene.store.Directory;
 
 /**
- * Random testing for RangeFieldQueries. Testing rigor inspired by {@code BaseGeoPointTestCase}
+ * Random testing for RangeFieldQueries.
  */
 public class TestDoubleRangeFieldQueries extends BaseRangeFieldQueryTestCase {
-  private static final String FIELD_NAME = "rangeField";
+  private static final String FIELD_NAME = "doubleRangeField";
+
+  private double nextDoubleInternal() {
+    if (rarely()) {
+      return random().nextBoolean() ? Double.POSITIVE_INFINITY : Double.NEGATIVE_INFINITY;
+    }
+    double max = Double.MAX_VALUE / 2;
+    return (max + max) * random().nextDouble() - max;
+  }
+
+  @Override
+  protected Range nextRange(int dimensions) {
+    double[] min = new double[dimensions];
+    double[] max = new double[dimensions];
+
+    for (int d=0; d<dimensions; ++d) {
+      min[d] = nextDoubleInternal();
+      max[d] = nextDoubleInternal();
+    }
 
-  protected DoubleRangeField newRangeField(double[] min, double[] max) {
-    return new DoubleRangeField(FIELD_NAME, min, max);
+    return new DoubleRange(min, max);
   }
 
-  protected Query newIntersectsQuery(double[] min, double[] max) {
-    return DoubleRangeField.newIntersectsQuery(FIELD_NAME, min, max);
+  @Override
+  protected DoubleRangeField newRangeField(Range r) {
+    return new DoubleRangeField(FIELD_NAME, ((DoubleRange)r).min, ((DoubleRange)r).max);
   }
 
-  protected Query newContainsQuery(double[] min, double[] max) {
-    return DoubleRangeField.newContainsQuery(FIELD_NAME, min, max);
+  @Override
+  protected Query newIntersectsQuery(Range r) {
+    return DoubleRangeField.newIntersectsQuery(FIELD_NAME, ((DoubleRange)r).min, ((DoubleRange)r).max);
   }
 
-  protected Query newWithinQuery(double[] min, double[] max) {
-    return DoubleRangeField.newWithinQuery(FIELD_NAME, min, max);
+  @Override
+  protected Query newContainsQuery(Range r) {
+    return DoubleRangeField.newContainsQuery(FIELD_NAME, ((DoubleRange)r).min, ((DoubleRange)r).max);
+  }
+
+  @Override
+  protected Query newWithinQuery(Range r) {
+    return DoubleRangeField.newWithinQuery(FIELD_NAME, ((DoubleRange)r).min, ((DoubleRange)r).max);
   }
 
   /** Basic test */
@@ -103,4 +130,111 @@ public class TestDoubleRangeFieldQueries extends BaseRangeFieldQueryTestCase {
     writer.close();
     dir.close();
   }
+
+  /** DoubleRange test class implementation - use to validate DoubleRangeField */
+  private class DoubleRange extends Range {
+    double[] min;
+    double[] max;
+
+    DoubleRange(double[] min, double[] max) {
+      assert min != null && max != null && min.length > 0 && max.length > 0
+          : "test box: min/max cannot be null or empty";
+      assert min.length == max.length : "test box: min/max length do not agree";
+      this.min = new double[min.length];
+      this.max = new double[max.length];
+      for (int d=0; d<min.length; ++d) {
+        if (min[d] > max[d]) {
+          // swap if max < min:
+          double temp = min[d];
+          min[d] = max[d];
+          max[d] = temp;
+        }
+      }
+    }
+
+    @Override
+    protected int numDimensions() {
+      return min.length;
+    }
+
+    @Override
+    protected Double getMin(int dim) {
+      return min[dim];
+    }
+
+    @Override
+    protected void setMin(int dim, Object val) {
+      min[dim] = (Double)val;
+    }
+
+    @Override
+    protected Double getMax(int dim) {
+      return max[dim];
+    }
+
+    @Override
+    protected void setMax(int dim, Object val) {
+      max[dim] = (Double)val;
+    }
+
+    @Override
+    protected boolean isEqual(Range other) {
+      DoubleRange o = (DoubleRange)other;
+      return Arrays.equals(min, o.min) && Arrays.equals(max, o.max);
+    }
+
+    @Override
+    protected boolean isDisjoint(Range o) {
+      DoubleRange other = (DoubleRange)o;
+      for (int d=0; d<this.min.length; ++d) {
+        if (this.min[d] > other.max[d] || this.max[d] < other.min[d]) {
+          // disjoint:
+          return true;
+        }
+      }
+      return false;
+    }
+
+    @Override
+    protected boolean isWithin(Range o) {
+      DoubleRange other = (DoubleRange)o;
+      for (int d=0; d<this.min.length; ++d) {
+        if ((this.min[d] >= other.min[d] && this.max[d] <= other.max[d]) == false) {
+          // not within:
+          return false;
+        }
+      }
+      return true;
+    }
+
+    @Override
+    protected boolean contains(Range o) {
+      DoubleRange other = (DoubleRange) o;
+      for (int d=0; d<this.min.length; ++d) {
+        if ((this.min[d] <= other.min[d] && this.max[d] >= other.max[d]) == false) {
+          // not contains:
+          return false;
+        }
+      }
+      return true;
+    }
+
+    @Override
+    public String toString() {
+      StringBuilder b = new StringBuilder();
+      b.append("Box(");
+      b.append(min[0]);
+      b.append(" TO ");
+      b.append(max[0]);
+      for (int d=1; d<min.length; ++d) {
+        b.append(", ");
+        b.append(min[d]);
+        b.append(" TO ");
+        b.append(max[d]);
+      }
+      b.append(")");
+
+      return b.toString();
+    }
+  }
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/249780cf/lucene/sandbox/src/test/org/apache/lucene/search/TestFloatRangeFieldQueries.java
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/test/org/apache/lucene/search/TestFloatRangeFieldQueries.java b/lucene/sandbox/src/test/org/apache/lucene/search/TestFloatRangeFieldQueries.java
new file mode 100644
index 0000000..c5a95d5
--- /dev/null
+++ b/lucene/sandbox/src/test/org/apache/lucene/search/TestFloatRangeFieldQueries.java
@@ -0,0 +1,240 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF 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.apache.lucene.search;
+
+import java.util.Arrays;
+
+import org.apache.lucene.document.Document;
+import org.apache.lucene.document.FloatRangeField;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.RandomIndexWriter;
+import org.apache.lucene.store.Directory;
+
+/**
+ * Random testing for FloatRangeField Queries.
+ */
+public class TestFloatRangeFieldQueries extends BaseRangeFieldQueryTestCase {
+  private static final String FIELD_NAME = "floatRangeField";
+
+  private float nextFloatInternal() {
+    if (rarely()) {
+      return random().nextBoolean() ? Float.NEGATIVE_INFINITY : Float.POSITIVE_INFINITY;
+    }
+    float max = Float.MAX_VALUE / 2;
+    return (max + max) * random().nextFloat() - max;
+  }
+
+  @Override
+  protected Range nextRange(int dimensions) {
+    float[] min = new float[dimensions];
+    float[] max = new float[dimensions];
+
+    for (int d=0; d<dimensions; ++d) {
+      min[d] = nextFloatInternal();
+      max[d] = nextFloatInternal();
+    }
+
+    return new FloatRange(min, max);
+  }
+
+  @Override
+  protected FloatRangeField newRangeField(Range r) {
+    return new FloatRangeField(FIELD_NAME, ((FloatRange)r).min, ((FloatRange)r).max);
+  }
+
+  @Override
+  protected Query newIntersectsQuery(Range r) {
+    return FloatRangeField.newIntersectsQuery(FIELD_NAME, ((FloatRange)r).min, ((FloatRange)r).max);
+  }
+
+  @Override
+  protected Query newContainsQuery(Range r) {
+    return FloatRangeField.newContainsQuery(FIELD_NAME, ((FloatRange)r).min, ((FloatRange)r).max);
+  }
+
+  @Override
+  protected Query newWithinQuery(Range r) {
+    return FloatRangeField.newWithinQuery(FIELD_NAME, ((FloatRange)r).min, ((FloatRange)r).max);
+  }
+
+  /** Basic test */
+  public void testBasics() throws Exception {
+    Directory dir = newDirectory();
+    RandomIndexWriter writer = new RandomIndexWriter(random(), dir);
+
+    // intersects (within)
+    Document document = new Document();
+    document.add(new FloatRangeField(FIELD_NAME, new float[] {-10.0f, -10.0f}, new float[] {9.1f, 10.1f}));
+    writer.addDocument(document);
+
+    // intersects (crosses)
+    document = new Document();
+    document.add(new FloatRangeField(FIELD_NAME, new float[] {10.0f, -10.0f}, new float[] {20.0f, 10.0f}));
+    writer.addDocument(document);
+
+    // intersects (contains)
+    document = new Document();
+    document.add(new FloatRangeField(FIELD_NAME, new float[] {-20.0f, -20.0f}, new float[] {30.0f, 30.1f}));
+    writer.addDocument(document);
+
+    // intersects (crosses)
+    document = new Document();
+    document.add(new FloatRangeField(FIELD_NAME, new float[] {-11.1f, -11.2f}, new float[] {1.23f, 11.5f}));
+    writer.addDocument(document);
+
+    // intersects (crosses)
+    document = new Document();
+    document.add(new FloatRangeField(FIELD_NAME, new float[] {12.33f, 1.2f}, new float[] {15.1f, 29.9f}));
+    writer.addDocument(document);
+
+    // disjoint
+    document = new Document();
+    document.add(new FloatRangeField(FIELD_NAME, new float[] {-122.33f, 1.2f}, new float[] {-115.1f, 29.9f}));
+    writer.addDocument(document);
+
+    // intersects (crosses)
+    document = new Document();
+    document.add(new FloatRangeField(FIELD_NAME, new float[] {Float.NEGATIVE_INFINITY, 1.2f}, new float[] {-11.0f, 29.9f}));
+    writer.addDocument(document);
+
+    // equal (within, contains, intersects)
+    document = new Document();
+    document.add(new FloatRangeField(FIELD_NAME, new float[] {-11f, -15f}, new float[] {15f, 20f}));
+    writer.addDocument(document);
+
+    // search
+    IndexReader reader = writer.getReader();
+    IndexSearcher searcher = newSearcher(reader);
+    assertEquals(7, searcher.count(FloatRangeField.newIntersectsQuery(FIELD_NAME,
+        new float[] {-11.0f, -15.0f}, new float[] {15.0f, 20.0f})));
+    assertEquals(2, searcher.count(FloatRangeField.newWithinQuery(FIELD_NAME,
+        new float[] {-11.0f, -15.0f}, new float[] {15.0f, 20.0f})));
+    assertEquals(2, searcher.count(FloatRangeField.newContainsQuery(FIELD_NAME,
+        new float[] {-11.0f, -15.0f}, new float[] {15.0f, 20.0f})));
+
+    reader.close();
+    writer.close();
+    dir.close();
+  }
+
+  /** FloatRange test class implementation - use to validate FloatRangeField */
+  private class FloatRange extends Range {
+    float[] min;
+    float[] max;
+
+    FloatRange(float[] min, float[] max) {
+      assert min != null && max != null && min.length > 0 && max.length > 0
+          : "test box: min/max cannot be null or empty";
+      assert min.length == max.length : "test box: min/max length do not agree";
+      this.min = new float[min.length];
+      this.max = new float[max.length];
+      for (int d=0; d<min.length; ++d) {
+        if (min[d] > max[d]) {
+          // swap if max < min:
+          float temp = min[d];
+          min[d] = max[d];
+          max[d] = temp;
+        }
+      }
+    }
+
+    @Override
+    protected int numDimensions() {
+      return min.length;
+    }
+
+    @Override
+    protected Float getMin(int dim) {
+      return min[dim];
+    }
+
+    @Override
+    protected void setMin(int dim, Object val) {
+      min[dim] = (Float)val;
+    }
+
+    @Override
+    protected Float getMax(int dim) {
+      return max[dim];
+    }
+
+    @Override
+    protected void setMax(int dim, Object val) {
+      max[dim] = (Float)val;
+    }
+
+    @Override
+    protected boolean isEqual(Range other) {
+      FloatRange o = (FloatRange)other;
+      return Arrays.equals(min, o.min) && Arrays.equals(max, o.max);
+    }
+
+    @Override
+    protected boolean isDisjoint(Range o) {
+      FloatRange other = (FloatRange)o;
+      for (int d=0; d<this.min.length; ++d) {
+        if (this.min[d] > other.max[d] || this.max[d] < other.min[d]) {
+          // disjoint:
+          return true;
+        }
+      }
+      return false;
+    }
+
+    @Override
+    protected boolean isWithin(Range o) {
+      FloatRange other = (FloatRange)o;
+      for (int d=0; d<this.min.length; ++d) {
+        if ((this.min[d] >= other.min[d] && this.max[d] <= other.max[d]) == false) {
+          // not within:
+          return false;
+        }
+      }
+      return true;
+    }
+
+    @Override
+    protected boolean contains(Range o) {
+      FloatRange other = (FloatRange) o;
+      for (int d=0; d<this.min.length; ++d) {
+        if ((this.min[d] <= other.min[d] && this.max[d] >= other.max[d]) == false) {
+          // not contains:
+          return false;
+        }
+      }
+      return true;
+    }
+
+    @Override
+    public String toString() {
+      StringBuilder b = new StringBuilder();
+      b.append("Box(");
+      b.append(min[0]);
+      b.append(" TO ");
+      b.append(max[0]);
+      for (int d=1; d<min.length; ++d) {
+        b.append(", ");
+        b.append(min[d]);
+        b.append(" TO ");
+        b.append(max[d]);
+      }
+      b.append(")");
+
+      return b.toString();
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/249780cf/lucene/sandbox/src/test/org/apache/lucene/search/TestIntRangeFieldQueries.java
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/test/org/apache/lucene/search/TestIntRangeFieldQueries.java b/lucene/sandbox/src/test/org/apache/lucene/search/TestIntRangeFieldQueries.java
new file mode 100644
index 0000000..6ec8def
--- /dev/null
+++ b/lucene/sandbox/src/test/org/apache/lucene/search/TestIntRangeFieldQueries.java
@@ -0,0 +1,240 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF 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.apache.lucene.search;
+
+import java.util.Arrays;
+
+import org.apache.lucene.document.Document;
+import org.apache.lucene.document.IntRangeField;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.RandomIndexWriter;
+import org.apache.lucene.store.Directory;
+
+/**
+ * Random testing for IntRangeField Queries.
+ */
+public class TestIntRangeFieldQueries extends BaseRangeFieldQueryTestCase {
+  private static final String FIELD_NAME = "intRangeField";
+
+  private int nextIntInternal() {
+    if (rarely()) {
+      return random().nextBoolean() ? Integer.MAX_VALUE : Integer.MIN_VALUE;
+    }
+    int max = Integer.MAX_VALUE / 2;
+    return (max + max) * random().nextInt() - max;
+  }
+
+  @Override
+  protected Range nextRange(int dimensions) {
+    int[] min = new int[dimensions];
+    int[] max = new int[dimensions];
+
+    for (int d=0; d<dimensions; ++d) {
+      min[d] = nextIntInternal();
+      max[d] = nextIntInternal();
+    }
+
+    return new IntRange(min, max);
+  }
+
+  @Override
+  protected IntRangeField newRangeField(Range r) {
+    return new IntRangeField(FIELD_NAME, ((IntRange)r).min, ((IntRange)r).max);
+  }
+
+  @Override
+  protected Query newIntersectsQuery(Range r) {
+    return IntRangeField.newIntersectsQuery(FIELD_NAME, ((IntRange)r).min, ((IntRange)r).max);
+  }
+
+  @Override
+  protected Query newContainsQuery(Range r) {
+    return IntRangeField.newContainsQuery(FIELD_NAME, ((IntRange)r).min, ((IntRange)r).max);
+  }
+
+  @Override
+  protected Query newWithinQuery(Range r) {
+    return IntRangeField.newWithinQuery(FIELD_NAME, ((IntRange)r).min, ((IntRange)r).max);
+  }
+
+  /** Basic test */
+  public void testBasics() throws Exception {
+    Directory dir = newDirectory();
+    RandomIndexWriter writer = new RandomIndexWriter(random(), dir);
+
+    // intersects (within)
+    Document document = new Document();
+    document.add(new IntRangeField(FIELD_NAME, new int[] {-10, -10}, new int[] {9, 10}));
+    writer.addDocument(document);
+
+    // intersects (crosses)
+    document = new Document();
+    document.add(new IntRangeField(FIELD_NAME, new int[] {10, -10}, new int[] {20, 10}));
+    writer.addDocument(document);
+
+    // intersects (contains)
+    document = new Document();
+    document.add(new IntRangeField(FIELD_NAME, new int[] {-20, -20}, new int[] {30, 30}));
+    writer.addDocument(document);
+
+    // intersects (within)
+    document = new Document();
+    document.add(new IntRangeField(FIELD_NAME, new int[] {-11, -11}, new int[] {1, 11}));
+    writer.addDocument(document);
+
+    // intersects (crosses)
+    document = new Document();
+    document.add(new IntRangeField(FIELD_NAME, new int[] {12, 1}, new int[] {15, 29}));
+    writer.addDocument(document);
+
+    // disjoint
+    document = new Document();
+    document.add(new IntRangeField(FIELD_NAME, new int[] {-122, 1}, new int[] {-115, 29}));
+    writer.addDocument(document);
+
+    // intersects (crosses)
+    document = new Document();
+    document.add(new IntRangeField(FIELD_NAME, new int[] {Integer.MIN_VALUE, 1}, new int[] {-11, 29}));
+    writer.addDocument(document);
+
+    // equal (within, contains, intersects)
+    document = new Document();
+    document.add(new IntRangeField(FIELD_NAME, new int[] {-11, -15}, new int[] {15, 20}));
+    writer.addDocument(document);
+
+    // search
+    IndexReader reader = writer.getReader();
+    IndexSearcher searcher = newSearcher(reader);
+    assertEquals(7, searcher.count(IntRangeField.newIntersectsQuery(FIELD_NAME,
+        new int[] {-11, -15}, new int[] {15, 20})));
+    assertEquals(3, searcher.count(IntRangeField.newWithinQuery(FIELD_NAME,
+        new int[] {-11, -15}, new int[] {15, 20})));
+    assertEquals(2, searcher.count(IntRangeField.newContainsQuery(FIELD_NAME,
+        new int[] {-11, -15}, new int[] {15, 20})));
+
+    reader.close();
+    writer.close();
+    dir.close();
+  }
+
+  /** IntRange test class implementation - use to validate IntRangeField */
+  private class IntRange extends Range {
+    int[] min;
+    int[] max;
+
+    IntRange(int[] min, int[] max) {
+      assert min != null && max != null && min.length > 0 && max.length > 0
+          : "test box: min/max cannot be null or empty";
+      assert min.length == max.length : "test box: min/max length do not agree";
+      this.min = new int[min.length];
+      this.max = new int[max.length];
+      for (int d=0; d<min.length; ++d) {
+        if (min[d] > max[d]) {
+          // swap if max < min:
+          int temp = min[d];
+          min[d] = max[d];
+          max[d] = temp;
+        }
+      }
+    }
+
+    @Override
+    protected int numDimensions() {
+      return min.length;
+    }
+
+    @Override
+    protected Integer getMin(int dim) {
+      return min[dim];
+    }
+
+    @Override
+    protected void setMin(int dim, Object val) {
+      min[dim] = (Integer)val;
+    }
+
+    @Override
+    protected Integer getMax(int dim) {
+      return max[dim];
+    }
+
+    @Override
+    protected void setMax(int dim, Object val) {
+      max[dim] = (Integer)val;
+    }
+
+    @Override
+    protected boolean isEqual(Range other) {
+      IntRange o = (IntRange)other;
+      return Arrays.equals(min, o.min) && Arrays.equals(max, o.max);
+    }
+
+    @Override
+    protected boolean isDisjoint(Range o) {
+      IntRange other = (IntRange)o;
+      for (int d=0; d<this.min.length; ++d) {
+        if (this.min[d] > other.max[d] || this.max[d] < other.min[d]) {
+          // disjoint:
+          return true;
+        }
+      }
+      return false;
+    }
+
+    @Override
+    protected boolean isWithin(Range o) {
+      IntRange other = (IntRange)o;
+      for (int d=0; d<this.min.length; ++d) {
+        if ((this.min[d] >= other.min[d] && this.max[d] <= other.max[d]) == false) {
+          // not within:
+          return false;
+        }
+      }
+      return true;
+    }
+
+    @Override
+    protected boolean contains(Range o) {
+      IntRange other = (IntRange) o;
+      for (int d=0; d<this.min.length; ++d) {
+        if ((this.min[d] <= other.min[d] && this.max[d] >= other.max[d]) == false) {
+          // not contains:
+          return false;
+        }
+      }
+      return true;
+    }
+
+    @Override
+    public String toString() {
+      StringBuilder b = new StringBuilder();
+      b.append("Box(");
+      b.append(min[0]);
+      b.append(" TO ");
+      b.append(max[0]);
+      for (int d=1; d<min.length; ++d) {
+        b.append(", ");
+        b.append(min[d]);
+        b.append(" TO ");
+        b.append(max[d]);
+      }
+      b.append(")");
+
+      return b.toString();
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/249780cf/lucene/sandbox/src/test/org/apache/lucene/search/TestLongRangeFieldQueries.java
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/test/org/apache/lucene/search/TestLongRangeFieldQueries.java b/lucene/sandbox/src/test/org/apache/lucene/search/TestLongRangeFieldQueries.java
new file mode 100644
index 0000000..558609b
--- /dev/null
+++ b/lucene/sandbox/src/test/org/apache/lucene/search/TestLongRangeFieldQueries.java
@@ -0,0 +1,240 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF 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.apache.lucene.search;
+
+import java.util.Arrays;
+
+import org.apache.lucene.document.Document;
+import org.apache.lucene.document.LongRangeField;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.RandomIndexWriter;
+import org.apache.lucene.store.Directory;
+
+/**
+ * Random testing for LongRangeField Queries.
+ */
+public class TestLongRangeFieldQueries extends BaseRangeFieldQueryTestCase {
+  private static final String FIELD_NAME = "longRangeField";
+
+  private long nextLongInternal() {
+    if (rarely()) {
+      return random().nextBoolean() ? Long.MAX_VALUE : Long.MIN_VALUE;
+    }
+    long max = Long.MAX_VALUE / 2;
+    return (max + max) * random().nextLong() - max;
+  }
+
+  @Override
+  protected Range nextRange(int dimensions) {
+    long[] min = new long[dimensions];
+    long[] max = new long[dimensions];
+
+    for (int d=0; d<dimensions; ++d) {
+      min[d] = nextLongInternal();
+      max[d] = nextLongInternal();
+    }
+
+    return new LongRange(min, max);
+  }
+
+  @Override
+  protected LongRangeField newRangeField(Range r) {
+    return new LongRangeField(FIELD_NAME, ((LongRange)r).min, ((LongRange)r).max);
+  }
+
+  @Override
+  protected Query newIntersectsQuery(Range r) {
+    return LongRangeField.newIntersectsQuery(FIELD_NAME, ((LongRange)r).min, ((LongRange)r).max);
+  }
+
+  @Override
+  protected Query newContainsQuery(Range r) {
+    return LongRangeField.newContainsQuery(FIELD_NAME, ((LongRange)r).min, ((LongRange)r).max);
+  }
+
+  @Override
+  protected Query newWithinQuery(Range r) {
+    return LongRangeField.newWithinQuery(FIELD_NAME, ((LongRange)r).min, ((LongRange)r).max);
+  }
+
+  /** Basic test */
+  public void testBasics() throws Exception {
+    Directory dir = newDirectory();
+    RandomIndexWriter writer = new RandomIndexWriter(random(), dir);
+
+    // intersects (within)
+    Document document = new Document();
+    document.add(new LongRangeField(FIELD_NAME, new long[] {-10, -10}, new long[] {9, 10}));
+    writer.addDocument(document);
+
+    // intersects (crosses)
+    document = new Document();
+    document.add(new LongRangeField(FIELD_NAME, new long[] {10, -10}, new long[] {20, 10}));
+    writer.addDocument(document);
+
+    // intersects (contains)
+    document = new Document();
+    document.add(new LongRangeField(FIELD_NAME, new long[] {-20, -20}, new long[] {30, 30}));
+    writer.addDocument(document);
+
+    // intersects (within)
+    document = new Document();
+    document.add(new LongRangeField(FIELD_NAME, new long[] {-11, -11}, new long[] {1, 11}));
+    writer.addDocument(document);
+
+    // intersects (crosses)
+    document = new Document();
+    document.add(new LongRangeField(FIELD_NAME, new long[] {12, 1}, new long[] {15, 29}));
+    writer.addDocument(document);
+
+    // disjoint
+    document = new Document();
+    document.add(new LongRangeField(FIELD_NAME, new long[] {-122, 1}, new long[] {-115, 29}));
+    writer.addDocument(document);
+
+    // intersects (crosses)
+    document = new Document();
+    document.add(new LongRangeField(FIELD_NAME, new long[] {Long.MIN_VALUE, 1}, new long[] {-11, 29}));
+    writer.addDocument(document);
+
+    // equal (within, contains, intersects)
+    document = new Document();
+    document.add(new LongRangeField(FIELD_NAME, new long[] {-11, -15}, new long[] {15, 20}));
+    writer.addDocument(document);
+
+    // search
+    IndexReader reader = writer.getReader();
+    IndexSearcher searcher = newSearcher(reader);
+    assertEquals(7, searcher.count(LongRangeField.newIntersectsQuery(FIELD_NAME,
+        new long[] {-11, -15}, new long[] {15, 20})));
+    assertEquals(3, searcher.count(LongRangeField.newWithinQuery(FIELD_NAME,
+        new long[] {-11, -15}, new long[] {15, 20})));
+    assertEquals(2, searcher.count(LongRangeField.newContainsQuery(FIELD_NAME,
+        new long[] {-11, -15}, new long[] {15, 20})));
+
+    reader.close();
+    writer.close();
+    dir.close();
+  }
+
+  /** LongRange test class implementation - use to validate LongRangeField */
+  private class LongRange extends Range {
+    long[] min;
+    long[] max;
+
+    LongRange(long[] min, long[] max) {
+      assert min != null && max != null && min.length > 0 && max.length > 0
+          : "test box: min/max cannot be null or empty";
+      assert min.length == max.length : "test box: min/max length do not agree";
+      this.min = new long[min.length];
+      this.max = new long[max.length];
+      for (int d=0; d<min.length; ++d) {
+        if (min[d] > max[d]) {
+          // swap if max < min:
+          long temp = min[d];
+          min[d] = max[d];
+          max[d] = temp;
+        }
+      }
+    }
+
+    @Override
+    protected int numDimensions() {
+      return min.length;
+    }
+
+    @Override
+    protected Long getMin(int dim) {
+      return min[dim];
+    }
+
+    @Override
+    protected void setMin(int dim, Object val) {
+      min[dim] = (Long)val;
+    }
+
+    @Override
+    protected Long getMax(int dim) {
+      return max[dim];
+    }
+
+    @Override
+    protected void setMax(int dim, Object val) {
+      max[dim] = (Long)val;
+    }
+
+    @Override
+    protected boolean isEqual(Range other) {
+      LongRange o = (LongRange)other;
+      return Arrays.equals(min, o.min) && Arrays.equals(max, o.max);
+    }
+
+    @Override
+    protected boolean isDisjoint(Range o) {
+      LongRange other = (LongRange)o;
+      for (int d=0; d<this.min.length; ++d) {
+        if (this.min[d] > other.max[d] || this.max[d] < other.min[d]) {
+          // disjoint:
+          return true;
+        }
+      }
+      return false;
+    }
+
+    @Override
+    protected boolean isWithin(Range o) {
+      LongRange other = (LongRange)o;
+      for (int d=0; d<this.min.length; ++d) {
+        if ((this.min[d] >= other.min[d] && this.max[d] <= other.max[d]) == false) {
+          // not within:
+          return false;
+        }
+      }
+      return true;
+    }
+
+    @Override
+    protected boolean contains(Range o) {
+      LongRange other = (LongRange) o;
+      for (int d=0; d<this.min.length; ++d) {
+        if ((this.min[d] <= other.min[d] && this.max[d] >= other.max[d]) == false) {
+          // not contains:
+          return false;
+        }
+      }
+      return true;
+    }
+
+    @Override
+    public String toString() {
+      StringBuilder b = new StringBuilder();
+      b.append("Box(");
+      b.append(min[0]);
+      b.append(" TO ");
+      b.append(max[0]);
+      for (int d=1; d<min.length; ++d) {
+        b.append(", ");
+        b.append(min[d]);
+        b.append(" TO ");
+        b.append(max[d]);
+      }
+      b.append(")");
+
+      return b.toString();
+    }
+  }
+}