You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by sa...@apache.org on 2016/03/20 21:57:07 UTC

lucene-solr:branch_6x: SOLR-8082: Can't query against negative float or double values when indexed='false' docValues='true' multiValued='false'

Repository: lucene-solr
Updated Branches:
  refs/heads/branch_6x ab821d3a0 -> 2668ff5ab


SOLR-8082: Can't query against negative float or double values when indexed='false' docValues='true' multiValued='false'


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

Branch: refs/heads/branch_6x
Commit: 2668ff5abb16e867f9f770d1da65457161b52cda
Parents: ab821d3
Author: Steve Rowe <sa...@apache.org>
Authored: Sun Mar 20 16:55:12 2016 -0400
Committer: Steve Rowe <sa...@apache.org>
Committed: Sun Mar 20 16:56:54 2016 -0400

----------------------------------------------------------------------
 solr/CHANGES.txt                                |   3 +
 .../java/org/apache/solr/schema/TrieField.java  |  63 ++-
 .../search/function/ValueSourceRangeFilter.java |   6 +-
 .../solr/collection1/conf/schema-docValues.xml  |   2 +-
 .../apache/solr/schema/DocValuesMultiTest.java  |  71 +++-
 .../org/apache/solr/schema/DocValuesTest.java   | 411 ++++++++++++++++---
 6 files changed, 490 insertions(+), 66 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/2668ff5a/solr/CHANGES.txt
----------------------------------------------------------------------
diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index 23b4a38..b386f6a 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -362,6 +362,9 @@ Optimizations
 
 * SOLR-8720: ZkController#publishAndWaitForDownStates should use #publishNodeAsDown. (Mark Miller)
 
+* SOLR-8082: Can't query against negative float or double values when indexed="false"
+  docValues="true" multiValued="false".  (hossman, Ishan Chattopadhyaya, yonik, Steve Rowe)
+
 Other Changes
 ----------------------
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/2668ff5a/solr/core/src/java/org/apache/solr/schema/TrieField.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/schema/TrieField.java b/solr/core/src/java/org/apache/solr/schema/TrieField.java
index c4899a1..1580b00 100644
--- a/solr/core/src/java/org/apache/solr/schema/TrieField.java
+++ b/solr/core/src/java/org/apache/solr/schema/TrieField.java
@@ -40,10 +40,13 @@ import org.apache.lucene.queries.function.valuesource.DoubleFieldSource;
 import org.apache.lucene.queries.function.valuesource.FloatFieldSource;
 import org.apache.lucene.queries.function.valuesource.IntFieldSource;
 import org.apache.lucene.queries.function.valuesource.LongFieldSource;
+import org.apache.lucene.search.BooleanQuery;
+import org.apache.lucene.search.BooleanQuery.Builder;
 import org.apache.lucene.search.DocValuesRangeQuery;
 import org.apache.lucene.search.LegacyNumericRangeQuery;
 import org.apache.lucene.search.Query;
 import org.apache.lucene.search.SortedSetSelector;
+import org.apache.lucene.search.BooleanClause.Occur;
 import org.apache.lucene.search.SortField;
 import org.apache.lucene.uninverting.UninvertingReader.Type;
 import org.apache.lucene.util.BytesRef;
@@ -56,7 +59,9 @@ import org.apache.lucene.util.mutable.MutableValueDate;
 import org.apache.lucene.util.mutable.MutableValueLong;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.response.TextResponseWriter;
+import org.apache.solr.search.FunctionRangeQuery;
 import org.apache.solr.search.QParser;
+import org.apache.solr.search.function.ValueSourceRangeFilter;
 import org.apache.solr.util.DateFormatUtil;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -351,10 +356,7 @@ public class TrieField extends PrimitiveFieldType {
         break;
       case FLOAT:
         if (matchOnly) {
-          query = DocValuesRangeQuery.newLongRange(field.getName(),
-                min == null ? null : (long) NumericUtils.floatToSortableInt(Float.parseFloat(min)),
-                max == null ? null : (long) NumericUtils.floatToSortableInt(Float.parseFloat(max)),
-                minInclusive, maxInclusive);
+          return getRangeQueryForFloatDoubleDocValues(field, min, max, minInclusive, maxInclusive);
         } else {
           query = LegacyNumericRangeQuery.newFloatRange(field.getName(), ps,
               min == null ? null : Float.parseFloat(min),
@@ -377,10 +379,7 @@ public class TrieField extends PrimitiveFieldType {
         break;
       case DOUBLE:
         if (matchOnly) {
-          query = DocValuesRangeQuery.newLongRange(field.getName(),
-                min == null ? null : NumericUtils.doubleToSortableLong(Double.parseDouble(min)),
-                max == null ? null : NumericUtils.doubleToSortableLong(Double.parseDouble(max)),
-                minInclusive, maxInclusive);
+          return getRangeQueryForFloatDoubleDocValues(field, min, max, minInclusive, maxInclusive);
         } else {
           query = LegacyNumericRangeQuery.newDoubleRange(field.getName(), ps,
               min == null ? null : Double.parseDouble(min),
@@ -407,7 +406,53 @@ public class TrieField extends PrimitiveFieldType {
 
     return query;
   }
-  
+
+  private static long FLOAT_NEGATIVE_INFINITY_BITS = (long)Float.floatToIntBits(Float.NEGATIVE_INFINITY);
+  private static long DOUBLE_NEGATIVE_INFINITY_BITS = Double.doubleToLongBits(Double.NEGATIVE_INFINITY);
+  private static long FLOAT_POSITIVE_INFINITY_BITS = (long)Float.floatToIntBits(Float.POSITIVE_INFINITY);
+  private static long DOUBLE_POSITIVE_INFINITY_BITS = Double.doubleToLongBits(Double.POSITIVE_INFINITY);
+  private static long FLOAT_MINUS_ZERO_BITS = (long)Float.floatToIntBits(-0f);
+  private static long DOUBLE_MINUS_ZERO_BITS = Double.doubleToLongBits(-0d);
+  private static long FLOAT_ZERO_BITS = (long)Float.floatToIntBits(0f);
+  private static long DOUBLE_ZERO_BITS = Double.doubleToLongBits(0d);
+
+  private Query getRangeQueryForFloatDoubleDocValues(SchemaField sf, String min, String max, boolean minInclusive, boolean maxInclusive) {
+    Query query;
+    String fieldName = sf.getName();
+
+    Number minVal = min == null ? null : type == TrieTypes.FLOAT ? Float.parseFloat(min): Double.parseDouble(min);
+    Number maxVal = max == null ? null : type == TrieTypes.FLOAT ? Float.parseFloat(max): Double.parseDouble(max);
+    
+    Long minBits = 
+        min == null ? null : type == TrieTypes.FLOAT ? (long) Float.floatToIntBits(minVal.floatValue()): Double.doubleToLongBits(minVal.doubleValue());
+    Long maxBits = 
+        max == null ? null : type == TrieTypes.FLOAT ? (long) Float.floatToIntBits(maxVal.floatValue()): Double.doubleToLongBits(maxVal.doubleValue());
+    
+    long negativeInfinityBits = type == TrieTypes.FLOAT ? FLOAT_NEGATIVE_INFINITY_BITS : DOUBLE_NEGATIVE_INFINITY_BITS;
+    long positiveInfinityBits = type == TrieTypes.FLOAT ? FLOAT_POSITIVE_INFINITY_BITS : DOUBLE_POSITIVE_INFINITY_BITS;
+    long minusZeroBits = type == TrieTypes.FLOAT ? FLOAT_MINUS_ZERO_BITS : DOUBLE_MINUS_ZERO_BITS;
+    long zeroBits = type == TrieTypes.FLOAT ? FLOAT_ZERO_BITS : DOUBLE_ZERO_BITS;
+    
+    // If min is negative (or -0d) and max is positive (or +0d), then issue a FunctionRangeQuery
+    if ((minVal == null || minVal.doubleValue() < 0d || minBits == minusZeroBits) && 
+        (maxVal == null || (maxVal.doubleValue() > 0d || maxBits == zeroBits))) {
+
+      ValueSource vs = getValueSource(sf, null);
+      query = new FunctionRangeQuery(new ValueSourceRangeFilter(vs, min, max, minInclusive, maxInclusive));
+
+    } else { // If both max and min are negative (or -0d), then issue range query with max and min reversed
+      if ((minVal == null || minVal.doubleValue() < 0d || minBits == minusZeroBits) &&
+          (maxVal != null && (maxVal.doubleValue() < 0d || maxBits == minusZeroBits))) {
+        query = DocValuesRangeQuery.newLongRange
+            (fieldName, maxBits, (min == null ? negativeInfinityBits : minBits), maxInclusive, minInclusive);
+      } else { // If both max and min are positive, then issue range query
+        query = DocValuesRangeQuery.newLongRange
+            (fieldName, minBits, (max == null ? positiveInfinityBits : maxBits), minInclusive, maxInclusive);
+      }
+    }
+    return query;
+  }
+
   @Override
   public Query getFieldQuery(QParser parser, SchemaField field, String externalVal) {
     if (!field.indexed() && field.hasDocValues()) {

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/2668ff5a/solr/core/src/java/org/apache/solr/search/function/ValueSourceRangeFilter.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/search/function/ValueSourceRangeFilter.java b/solr/core/src/java/org/apache/solr/search/function/ValueSourceRangeFilter.java
index 64211d2..1fce97e 100644
--- a/solr/core/src/java/org/apache/solr/search/function/ValueSourceRangeFilter.java
+++ b/solr/core/src/java/org/apache/solr/search/function/ValueSourceRangeFilter.java
@@ -48,8 +48,10 @@ public class ValueSourceRangeFilter extends SolrFilter {
     this.valueSource = valueSource;
     this.lowerVal = lowerVal;
     this.upperVal = upperVal;
-    this.includeLower = lowerVal != null && includeLower;
-    this.includeUpper = upperVal != null && includeUpper;
+    this.includeLower = includeLower;
+    this.includeUpper = includeUpper;
+//    this.includeLower = lowerVal != null && includeLower;
+//    this.includeUpper = upperVal != null && includeUpper;
   }
 
   public ValueSource getValueSource() {

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/2668ff5a/solr/core/src/test-files/solr/collection1/conf/schema-docValues.xml
----------------------------------------------------------------------
diff --git a/solr/core/src/test-files/solr/collection1/conf/schema-docValues.xml b/solr/core/src/test-files/solr/collection1/conf/schema-docValues.xml
index 851ff65..cbbdf6e 100644
--- a/solr/core/src/test-files/solr/collection1/conf/schema-docValues.xml
+++ b/solr/core/src/test-files/solr/collection1/conf/schema-docValues.xml
@@ -58,7 +58,7 @@
 
   <fields>
 
-    <field name="id" type="string" required="true" />
+    <field name="id" type="int" required="true" />
 
     <field name="floatdv" type="float" indexed="false" stored="false" docValues="true" default="1" />
     <field name="intdv" type="int" indexed="false" stored="false" docValues="true" default="2" />

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/2668ff5a/solr/core/src/test/org/apache/solr/schema/DocValuesMultiTest.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/schema/DocValuesMultiTest.java b/solr/core/src/test/org/apache/solr/schema/DocValuesMultiTest.java
index b544d53..7b4a5db 100644
--- a/solr/core/src/test/org/apache/solr/schema/DocValuesMultiTest.java
+++ b/solr/core/src/test/org/apache/solr/schema/DocValuesMultiTest.java
@@ -33,6 +33,18 @@ public class DocValuesMultiTest extends SolrTestCaseJ4 {
   @BeforeClass
   public static void beforeTests() throws Exception {
     initCore("solrconfig-basic.xml", "schema-docValuesMulti.xml");
+    
+    // sanity check our schema meets our expectations
+    final IndexSchema schema = h.getCore().getLatestSchema();
+    for (String f : new String[] {"floatdv", "intdv", "doubledv", "longdv", "datedv", "stringdv"}) {
+      final SchemaField sf = schema.getField(f);
+      assertTrue(f + " is not multiValued, test is useless, who changed the schema?",
+                 sf.multiValued());
+      assertFalse(f + " is indexed, test is useless, who changed the schema?",
+                  sf.indexed());
+      assertTrue(f + " has no docValues, test is useless, who changed the schema?",
+                 sf.hasDocValues());
+    }
   }
 
   public void setUp() throws Exception {
@@ -117,9 +129,10 @@ public class DocValuesMultiTest extends SolrTestCaseJ4 {
    */
   public void testFloatDocValuesMatch() throws Exception {
     assertU(adoc("id", "1", "floatdv", "2"));
-    assertU(adoc("id", "2", "floatdv", "5"));
+    assertU(adoc("id", "2", "floatdv", "-5"));
     assertU(adoc("id", "3", "floatdv", "3.0", "floatdv", "-1.3", "floatdv", "2.2"));
     assertU(adoc("id", "4", "floatdv", "3"));
+    assertU(adoc("id", "5", "floatdv", "-0.5"));
     assertU(commit());
     
     // float: termquery
@@ -131,10 +144,62 @@ public class DocValuesMultiTest extends SolrTestCaseJ4 {
     
     // float: rangequery
     assertQ(req("q", "floatdv:[-1 TO 2.5]", "sort", "id asc"),
+            "//*[@numFound='3']",
+            "//result/doc[1]/str[@name='id'][.=1]",
+            "//result/doc[2]/str[@name='id'][.=3]",
+            "//result/doc[3]/str[@name='id'][.=5]"
+    );
+
+    // (neg) float: rangequery
+    assertQ(req("q", "floatdv:[-6 TO -4]", "sort", "id asc"),
+            "//*[@numFound='1']",
+            "//result/doc[1]/str[@name='id'][.=2]"
+            );
+    
+    // (neg) float: termquery
+    assertQ(req("q", "floatdv:\"-5\"", "sort", "id asc"),
+            "//*[@numFound='1']",
+            "//result/doc[1]/str[@name='id'][.=2]"
+            );
+  }
+  
+  /** Tests the ability to do basic queries (without scoring, just match-only) on
+   *  double docvalues fields that are not inverted (indexed "forward" only)
+   */
+  public void testDoubleDocValuesMatch() throws Exception {
+    assertU(adoc("id", "1", "doubledv", "2"));
+    assertU(adoc("id", "2", "doubledv", "-5"));
+    assertU(adoc("id", "3", "doubledv", "3.0", "doubledv", "-1.3", "doubledv", "2.2"));
+    assertU(adoc("id", "4", "doubledv", "3"));
+    assertU(adoc("id", "5", "doubledv", "-0.5"));
+    assertU(commit());
+    
+    // double: termquery
+    assertQ(req("q", "doubledv:3", "sort", "id asc"),
         "//*[@numFound='2']",
-        "//result/doc[1]/str[@name='id'][.=1]",
-        "//result/doc[2]/str[@name='id'][.=3]"
+        "//result/doc[1]/str[@name='id'][.=3]",
+        "//result/doc[2]/str[@name='id'][.=4]"
     );
+    
+    // double: rangequery
+    assertQ(req("q", "doubledv:[-1 TO 2.5]", "sort", "id asc"),
+            "//*[@numFound='3']",
+            "//result/doc[1]/str[@name='id'][.=1]",
+            "//result/doc[2]/str[@name='id'][.=3]",
+            "//result/doc[3]/str[@name='id'][.=5]"
+    );
+
+    // (neg) double: rangequery
+    assertQ(req("q", "doubledv:[-6 TO -4]", "sort", "id asc"),
+            "//*[@numFound='1']",
+            "//result/doc[1]/str[@name='id'][.=2]"
+            );
+    
+    // (neg) double: termquery
+    assertQ(req("q", "doubledv:\"-5\"", "sort", "id asc"),
+            "//*[@numFound='1']",
+            "//result/doc[1]/str[@name='id'][.=2]"
+            );
   }
   
   public void testDocValuesFacetingSimple() {

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/2668ff5a/solr/core/src/test/org/apache/solr/schema/DocValuesTest.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/schema/DocValuesTest.java b/solr/core/src/test/org/apache/solr/schema/DocValuesTest.java
index a9aa401..0c3b002 100644
--- a/solr/core/src/test/org/apache/solr/schema/DocValuesTest.java
+++ b/solr/core/src/test/org/apache/solr/schema/DocValuesTest.java
@@ -20,6 +20,7 @@ import org.apache.lucene.index.LeafReader;
 import org.apache.lucene.index.DocValuesType;
 import org.apache.lucene.index.FieldInfos;
 import org.apache.lucene.queries.function.FunctionValues;
+import org.apache.lucene.util.NumericUtils;
 import org.apache.solr.SolrTestCaseJ4;
 import org.apache.solr.core.SolrCore;
 import org.apache.solr.search.SolrIndexSearcher;
@@ -27,12 +28,32 @@ import org.apache.solr.util.RefCounted;
 import org.junit.BeforeClass;
 
 import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.Function;
+import java.util.function.Supplier;
+import java.util.logging.Logger;
 
 public class DocValuesTest extends SolrTestCaseJ4 {
 
+  protected Logger log = Logger.getLogger(getClass().getName());
+
   @BeforeClass
   public static void beforeTests() throws Exception {
     initCore("solrconfig-basic.xml", "schema-docValues.xml");
+
+    // sanity check our schema meets our expectations
+    final IndexSchema schema = h.getCore().getLatestSchema();
+    for (String f : new String[] {"floatdv", "intdv", "doubledv", "longdv", "datedv", "stringdv"}) {
+      final SchemaField sf = schema.getField(f);
+      assertFalse(f + " is multiValued, test is useless, who changed the schema?",
+                  sf.multiValued());
+      assertFalse(f + " is indexed, test is useless, who changed the schema?",
+                  sf.indexed());
+      assertTrue(f + " has no docValues, test is useless, who changed the schema?",
+                 sf.hasDocValues());
+    }
   }
 
   public void setUp() throws Exception {
@@ -92,29 +113,29 @@ public class DocValuesTest extends SolrTestCaseJ4 {
     assertU(adoc("id", "4"));
     assertU(commit());
     assertQ(req("q", "*:*", "sort", "floatdv desc", "rows", "1", "fl", "id"),
-        "//str[@name='id'][.='2']");
+        "//int[@name='id'][.='2']");
     assertQ(req("q", "*:*", "sort", "intdv desc", "rows", "1", "fl", "id"),
-        "//str[@name='id'][.='2']");
+        "//int[@name='id'][.='2']");
     assertQ(req("q", "*:*", "sort", "doubledv desc", "rows", "1", "fl", "id"),
-        "//str[@name='id'][.='1']");
+        "//int[@name='id'][.='1']");
     assertQ(req("q", "*:*", "sort", "longdv desc", "rows", "1", "fl", "id"),
-        "//str[@name='id'][.='1']");
+        "//int[@name='id'][.='1']");
     assertQ(req("q", "*:*", "sort", "datedv desc", "rows", "1", "fl", "id"),
-        "//str[@name='id'][.='2']");
+        "//int[@name='id'][.='2']");
     assertQ(req("q", "*:*", "sort", "stringdv desc", "rows", "1", "fl", "id"),
-        "//str[@name='id'][.='4']");
+        "//int[@name='id'][.='4']");
     assertQ(req("q", "*:*", "sort", "floatdv asc", "rows", "1", "fl", "id"),
-        "//str[@name='id'][.='4']");
+        "//int[@name='id'][.='4']");
     assertQ(req("q", "*:*", "sort", "intdv asc", "rows", "1", "fl", "id"),
-        "//str[@name='id'][.='3']");
+        "//int[@name='id'][.='3']");
     assertQ(req("q", "*:*", "sort", "doubledv asc", "rows", "1", "fl", "id"),
-        "//str[@name='id'][.='3']");
+        "//int[@name='id'][.='3']");
     assertQ(req("q", "*:*", "sort", "longdv asc", "rows", "1", "fl", "id"),
-        "//str[@name='id'][.='3']");
+        "//int[@name='id'][.='3']");
     assertQ(req("q", "*:*", "sort", "datedv asc", "rows", "1", "fl", "id"),
-        "//str[@name='id'][.='1']");
+        "//int[@name='id'][.='1']");
     assertQ(req("q", "*:*", "sort", "stringdv asc", "rows", "1", "fl", "id"),
-        "//str[@name='id'][.='2']");
+        "//int[@name='id'][.='2']");
   }
   
   public void testDocValuesSorting2() {
@@ -122,18 +143,18 @@ public class DocValuesTest extends SolrTestCaseJ4 {
     assertU(adoc("id", "2", "doubledv", "50.567"));
     assertU(adoc("id", "3", "doubledv", "+0"));
     assertU(adoc("id", "4", "doubledv", "4.9E-324"));
-    assertU(adoc("id", "5", "doubledv", "-0"));
+    assertU(adoc("id", "5", "doubledv", "-0.1"));
     assertU(adoc("id", "6", "doubledv", "-5.123"));
     assertU(adoc("id", "7", "doubledv", "1.7976931348623157E308"));
     assertU(commit());
     assertQ(req("fl", "id", "q", "*:*", "sort", "doubledv asc"),
-        "//result/doc[1]/str[@name='id'][.='6']",
-        "//result/doc[2]/str[@name='id'][.='5']",
-        "//result/doc[3]/str[@name='id'][.='3']",
-        "//result/doc[4]/str[@name='id'][.='4']",
-        "//result/doc[5]/str[@name='id'][.='1']",
-        "//result/doc[6]/str[@name='id'][.='2']",
-        "//result/doc[7]/str[@name='id'][.='7']"
+        "//result/doc[1]/int[@name='id'][.='6']",
+        "//result/doc[2]/int[@name='id'][.='5']",
+        "//result/doc[3]/int[@name='id'][.='3']",
+        "//result/doc[4]/int[@name='id'][.='4']",
+        "//result/doc[5]/int[@name='id'][.='1']",
+        "//result/doc[6]/int[@name='id'][.='2']",
+        "//result/doc[7]/int[@name='id'][.='7']"
         );
   }
 
@@ -247,87 +268,375 @@ public class DocValuesTest extends SolrTestCaseJ4 {
    *  docvalues fields that are not inverted (indexed "forward" only)
    */
   public void testDocValuesMatch() throws Exception {
-    assertU(adoc("id", "1", "floatdv", "2", "intdv", "3", "doubledv", "4", "longdv", "5", "datedv", "1995-12-31T23:59:59.999Z", "stringdv", "b"));
-    assertU(adoc("id", "2", "floatdv", "5", "intdv", "4", "doubledv", "3", "longdv", "2", "datedv", "1997-12-31T23:59:59.999Z", "stringdv", "a"));
-    assertU(adoc("id", "3", "floatdv", "3", "intdv", "1", "doubledv", "2", "longdv", "1", "datedv", "1996-12-31T23:59:59.999Z", "stringdv", "c"));
-    assertU(adoc("id", "4", "floatdv", "3", "intdv", "1", "doubledv", "2", "longdv", "1", "datedv", "1996-12-31T23:59:59.999Z", "stringdv", "car"));
+    assertU(adoc("id", "1", "floatdv", "2", "intdv", "3", "doubledv", "3.1", "longdv", "5", "datedv", "1995-12-31T23:59:59.999Z", "stringdv", "b"));
+    assertU(adoc("id", "2", "floatdv", "-5", "intdv", "4", "doubledv", "-4.3", "longdv", "2", "datedv", "1997-12-31T23:59:59.999Z", "stringdv", "a"));
+    assertU(adoc("id", "3", "floatdv", "3", "intdv", "1", "doubledv", "2.1", "longdv", "1", "datedv", "1996-12-31T23:59:59.999Z", "stringdv", "c"));
+    assertU(adoc("id", "4", "floatdv", "3", "intdv", "-1", "doubledv", "1.5", "longdv", "1", "datedv", "1996-12-31T23:59:59.999Z", "stringdv", "car"));
     assertU(commit());
-    
+
     // string: termquery
     assertQ(req("q", "stringdv:car", "sort", "id asc"),
         "//*[@numFound='1']",
-        "//result/doc[1]/str[@name='id'][.=4]"
+        "//result/doc[1]/int[@name='id'][.=4]"
     );
     
     // string: range query
     assertQ(req("q", "stringdv:[b TO d]", "sort", "id asc"),
         "//*[@numFound='3']",
-        "//result/doc[1]/str[@name='id'][.=1]",
-        "//result/doc[2]/str[@name='id'][.=3]",
-        "//result/doc[3]/str[@name='id'][.=4]"
+        "//result/doc[1]/int[@name='id'][.=1]",
+        "//result/doc[2]/int[@name='id'][.=3]",
+        "//result/doc[3]/int[@name='id'][.=4]"
     );
     
     // string: prefix query
     assertQ(req("q", "stringdv:c*", "sort", "id asc"),
         "//*[@numFound='2']",
-        "//result/doc[1]/str[@name='id'][.=3]",
-        "//result/doc[2]/str[@name='id'][.=4]"
+        "//result/doc[1]/int[@name='id'][.=3]",
+        "//result/doc[2]/int[@name='id'][.=4]"
     );
     
     // string: wildcard query
     assertQ(req("q", "stringdv:c?r", "sort", "id asc"),
         "//*[@numFound='1']",
-        "//result/doc[1]/str[@name='id'][.=4]"
+        "//result/doc[1]/int[@name='id'][.=4]"
     );
     
     // string: regexp query
     assertQ(req("q", "stringdv:/c[a-b]r/", "sort", "id asc"),
         "//*[@numFound='1']",
-        "//result/doc[1]/str[@name='id'][.=4]"
+        "//result/doc[1]/int[@name='id'][.=4]"
     );
     
     // float: termquery
     assertQ(req("q", "floatdv:3", "sort", "id asc"),
         "//*[@numFound='2']",
-        "//result/doc[1]/str[@name='id'][.=3]",
-        "//result/doc[2]/str[@name='id'][.=4]"
+        "//result/doc[1]/int[@name='id'][.=3]",
+        "//result/doc[2]/int[@name='id'][.=4]"
     );
     
     // float: rangequery
     assertQ(req("q", "floatdv:[2 TO 3]", "sort", "id asc"),
         "//*[@numFound='3']",
-        "//result/doc[1]/str[@name='id'][.=1]",
-        "//result/doc[2]/str[@name='id'][.=3]",
-        "//result/doc[3]/str[@name='id'][.=4]"
+        "//result/doc[1]/int[@name='id'][.=1]",
+        "//result/doc[2]/int[@name='id'][.=3]",
+        "//result/doc[3]/int[@name='id'][.=4]"
     );
     
+    // (neg) float: termquery
+    assertQ(req("q", "floatdv:\"-5\"", "sort", "id asc"),
+            "//*[@numFound='1']",
+            "//result/doc[1]/int[@name='id'][.=2]"
+            );
+
+    // (neg) float: rangequery
+    assertQ(req("q", "floatdv:[-6 TO -4]", "sort", "id asc"),
+            "//*[@numFound='1']",
+            "//result/doc[1]/int[@name='id'][.=2]"
+            );
+    
+    // (cross zero bounds) float: rangequery
+    assertQ(req("q", "floatdv:[-6 TO 2.1]", "sort", "id asc"),
+            "//*[@numFound='2']",
+            "//result/doc[1]/int[@name='id'][.=1]",
+            "//result/doc[2]/int[@name='id'][.=2]"
+            );
+    
     // int: termquery
     assertQ(req("q", "intdv:1", "sort", "id asc"),
-        "//*[@numFound='2']",
-        "//result/doc[1]/str[@name='id'][.=3]",
-        "//result/doc[2]/str[@name='id'][.=4]"
-    );
+            "//*[@numFound='1']",
+            "//result/doc[1]/int[@name='id'][.=3]"
+            );
     
     // int: rangequery
     assertQ(req("q", "intdv:[3 TO 4]", "sort", "id asc"),
-        "//*[@numFound='2']",
-        "//result/doc[1]/str[@name='id'][.=1]",
-        "//result/doc[2]/str[@name='id'][.=2]"
-    );
+            "//*[@numFound='2']",
+            "//result/doc[1]/int[@name='id'][.=1]",
+            "//result/doc[2]/int[@name='id'][.=2]"
+            );
+    
+    // (neg) int: termquery
+    assertQ(req("q", "intdv:\"-1\"", "sort", "id asc"),
+            "//*[@numFound='1']",
+            "//result/doc[1]/int[@name='id'][.=4]"
+            );
     
+    // (neg) int: rangequery
+    assertQ(req("q", "intdv:[-1 TO 1]", "sort", "id asc"),
+            "//*[@numFound='2']",
+            "//result/doc[1]/int[@name='id'][.=3]",
+            "//result/doc[2]/int[@name='id'][.=4]"
+            );
+
     // long: termquery
     assertQ(req("q", "longdv:1", "sort", "id asc"),
         "//*[@numFound='2']",
-        "//result/doc[1]/str[@name='id'][.=3]",
-        "//result/doc[2]/str[@name='id'][.=4]"
+        "//result/doc[1]/int[@name='id'][.=3]",
+        "//result/doc[2]/int[@name='id'][.=4]"
     );
     
     // long: rangequery
     assertQ(req("q", "longdv:[1 TO 2]", "sort", "id asc"),
         "//*[@numFound='3']",
-        "//result/doc[1]/str[@name='id'][.=2]",
-        "//result/doc[2]/str[@name='id'][.=3]",
-        "//result/doc[3]/str[@name='id'][.=4]"
+        "//result/doc[1]/int[@name='id'][.=2]",
+        "//result/doc[2]/int[@name='id'][.=3]",
+        "//result/doc[3]/int[@name='id'][.=4]"
     );
+    
+    // double: termquery
+    assertQ(req("q", "doubledv:3.1", "sort", "id asc"),
+            "//*[@numFound='1']",
+            "//result/doc[1]/int[@name='id'][.=1]"
+            );
+    
+    // double: rangequery
+    assertQ(req("q", "doubledv:[2 TO 3.3]", "sort", "id asc"),
+            "//*[@numFound='2']",
+            "//result/doc[1]/int[@name='id'][.=1]",
+            "//result/doc[2]/int[@name='id'][.=3]"
+            );
+    
+    // (neg) double: termquery
+    assertQ(req("q", "doubledv:\"-4.3\"", "sort", "id asc"),
+            "//*[@numFound='1']",
+            "//result/doc[1]/int[@name='id'][.=2]"
+            );
+    
+    // (neg) double: rangequery
+    assertQ(req("q", "doubledv:[-6 TO -4]", "sort", "id asc"),
+            "//*[@numFound='1']",
+            "//result/doc[1]/int[@name='id'][.=2]"
+            );
+    
+    // (cross zero bounds) double: rangequery
+    assertQ(req("q", "doubledv:[-6 TO 2.0]", "sort", "id asc"),
+            "//*[@numFound='2']",
+            "//result/doc[1]/int[@name='id'][.=2]",
+            "//result/doc[2]/int[@name='id'][.=4]"
+            );
+  }
+
+  public void testFloatAndDoubleRangeQueryRandom() throws Exception {
+
+    String fieldName[] = new String[] {"floatdv", "doubledv"};
+    
+    Number largestNegative[] = new Number[] {0f-Float.MIN_NORMAL, 0f-Double.MIN_NORMAL};
+    Number smallestPositive[] = new Number[] {Float.MIN_NORMAL, Double.MIN_NORMAL};
+    Number positiveInfinity[] = new Number[] {Float.POSITIVE_INFINITY, Double.POSITIVE_INFINITY};
+    Number negativeInfinity[] = new Number[] {Float.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY};
+    Number largestValue[] = new Number[] {Float.MAX_VALUE, Double.MAX_VALUE};
+    Number zero[] = new Number[] {0f, 0d};
+    Function<Supplier<Number>,Number> noNaN = (next)
+        -> { Number num; while (String.valueOf(num = next.get()).equals("NaN")); return num; };
+    List<Supplier<Number>> nextRandNoNaN = Arrays.asList(
+        () -> noNaN.apply(() -> Float.intBitsToFloat(random().nextInt())),
+        () -> noNaN.apply(() -> Double.longBitsToDouble(random().nextLong())));
+    List<Function<Number,Long>> toSortableLong = Arrays.asList(
+        (num) -> (long)NumericUtils.floatToSortableInt(num.floatValue()),
+        (num) -> NumericUtils.doubleToSortableLong(num.doubleValue()));
+
+    // Number minusZero[] = new Number[] {-0f, -0d}; // -0 == 0, so we should not treat them differently (and we should not guarantee that sign is preserved... we should be able to index both as 0)
+
+    for (int i=0; i<fieldName.length; i++) {
+      assertU(delQ("*:*"));
+      commit();
+
+      Number specialValues[] = new Number[] {largestNegative[i], smallestPositive[i], negativeInfinity[i], 
+          largestValue[i], positiveInfinity[i], zero[i]};
+
+      List<Number> values = new ArrayList<>();
+      int numDocs = 1 + random().nextInt(10);
+      for (int j=0; j<numDocs; j++) {
+        
+        if (random().nextInt(100) < 5) { // Add a boundary value with 5% probability
+          values.add(specialValues[random().nextInt(specialValues.length)]);
+        } else 
+        {
+          if (fieldName[i].equals("floatdv")) { // Add random values with 95% probability
+            values.add(Float.intBitsToFloat(random().nextInt()));
+          } else {
+            values.add(Double.longBitsToDouble(random().nextLong()));
+          }
+        }
+      }
+      // Indexing
+      for (int j=0; j<values.size(); j++) {
+        assertU(adoc("id", String.valueOf(j+1), fieldName[i], String.valueOf(values.get(j))));
+      }
+      assertU(commit());
+
+      log.info("Indexed values: "+values);
+      // Querying
+      int numQueries = 10000;
+      for (int j=0; j<numQueries; j++) {
+        boolean minInclusive = random().nextBoolean();
+        boolean maxInclusive = random().nextBoolean();
+
+        Number minVal, maxVal;
+        String min = String.valueOf(minVal = nextRandNoNaN.get(i).get());
+        String max = String.valueOf(maxVal = nextRandNoNaN.get(i).get());
+
+        // randomly use boundary values for min, 15% of the time
+        int r = random().nextInt(100);
+        if (r<5) {
+          minVal = negativeInfinity[i]; min = "*";
+        } else if (r<10) {
+          minVal = specialValues[random().nextInt(specialValues.length)]; min = String.valueOf(minVal);
+        } else if (r<15) {
+          minVal = values.get(random().nextInt(values.size())); min = String.valueOf(minVal);
+        }
+
+        // randomly use boundary values for max, 15% of the time
+        r = random().nextInt(100);
+        if (r<5) {
+          maxVal = positiveInfinity[i]; max = "*";
+        } else if (r<10) {
+            maxVal = specialValues[random().nextInt(specialValues.length)]; max = String.valueOf(maxVal);
+        } else if (r<15) {
+          // Don't pick a NaN for the range query
+          Number tmp = values.get(random().nextInt(values.size()));
+          if (!Double.isNaN(tmp.doubleValue()) && !Float.isNaN(tmp.floatValue())) {
+            maxVal = tmp; max = String.valueOf(maxVal);
+          }
+        }
+
+        List<String> tests = new ArrayList<>();
+        int counter = 0;
+        
+        for (int k=0; k<values.size(); k++) {
+          Number val = values.get(k);
+          long valSortable = toSortableLong.get(i).apply(val);
+          long minSortable = toSortableLong.get(i).apply(minVal);
+          long maxSortable = toSortableLong.get(i).apply(maxVal);
+          
+          if((minInclusive && minSortable<=valSortable || !minInclusive && minSortable<valSortable) &&
+              (maxInclusive && maxSortable>=valSortable || !maxInclusive && maxSortable>valSortable)) {
+            counter++;
+            tests.add("//result/doc["+counter+"]/int[@name='id'][.="+(k+1)+"]");
+            tests.add("//result/doc["+counter+"]/float[@name='score'][.=1.0]");
+          }
+        }
+
+        tests.add(0, "//*[@numFound='"+counter+"']");
+
+        String testsArr[] = new String[tests.size()];
+        for (int k=0; k<tests.size(); k++) {
+          testsArr[k] = tests.get(k);
+        }
+        log.info("Expected: "+tests);
+        assertQ(req("q", fieldName[i] + ":" + (minInclusive? '[': '{') + min + " TO " + max + (maxInclusive? ']': '}'),
+                         "sort", "id asc", "fl", "id,"+fieldName[i]+",score"),
+            testsArr);
+      }
+    }
+  }
+
+  public void testFloatAndDoubleRangeQuery() throws Exception {
+    String fieldName[] = new String[] {"floatdv", "doubledv"};
+    String largestNegative[] = new String[] {String.valueOf(0f-Float.MIN_NORMAL), String.valueOf(0f-Double.MIN_NORMAL)};
+    String negativeInfinity[] = new String[] {String.valueOf(Float.NEGATIVE_INFINITY), String.valueOf(Double.NEGATIVE_INFINITY)};
+    String largestValue[] = new String[] {String.valueOf(Float.MAX_VALUE), String.valueOf(Double.MAX_VALUE)};
+    
+    for (int i=0; i<fieldName.length; i++) {
+      assertU(adoc("id", "1", fieldName[i], "2"));
+      assertU(adoc("id", "2", fieldName[i], "-5"));
+      assertU(adoc("id", "3", fieldName[i], "3"));
+      assertU(adoc("id", "4", fieldName[i], "3"));
+      assertU(adoc("id", "5", fieldName[i], largestNegative[i]));
+      assertU(adoc("id", "6", fieldName[i], negativeInfinity[i]));
+      assertU(adoc("id", "7", fieldName[i], largestValue[i]));
+      assertU(commit());
+
+      // Negative Zero to Positive
+      assertQ(req("q", fieldName[i]+":[-0.0 TO 2.5]", "sort", "id asc", "fl", "id,"+fieldName[i]+",score"),
+          "//*[@numFound='1']",
+          "//result/doc[1]/int[@name='id'][.=1]"
+          );
+
+      // Negative to Positive Zero
+      assertQ(req("q", fieldName[i]+":[-6 TO 0]", "sort", "id asc", "fl", "id,"+fieldName[i]+",score"),
+          "//*[@numFound='2']",
+          "//result/doc[1]/int[@name='id'][.=2]",
+          "//result/doc[2]/int[@name='id'][.=5]"
+          );
+
+      // Negative to Positive
+      assertQ(req("q", fieldName[i]+":[-6 TO 2.5]", "sort", "id asc", "fl", "id,"+fieldName[i]+",score"),
+          "//*[@numFound='3']",
+          "//result/doc[1]/int[@name='id'][.=1]",
+          "//result/doc[2]/int[@name='id'][.=2]",
+          "//result/doc[3]/int[@name='id'][.=5]"
+          );
+
+      // Positive to Positive
+      assertQ(req("q", fieldName[i]+":[2 TO 3]", "sort", "id asc", "fl", "id,"+fieldName[i]+",score"),
+          "//*[@numFound='3']",
+          "//result/doc[1]/int[@name='id'][.=1]",
+          "//result/doc[2]/int[@name='id'][.=3]",
+          "//result/doc[3]/int[@name='id'][.=4]"
+          );
+
+      // Positive to POSITIVE_INF
+      assertQ(req("q", fieldName[i]+":[2 TO *]", "sort", "id asc", "fl", "id,"+fieldName[i]+",score"),
+          "//*[@numFound='4']",
+          "//result/doc[1]/int[@name='id'][.=1]",
+          "//result/doc[2]/int[@name='id'][.=3]",
+          "//result/doc[3]/int[@name='id'][.=4]",
+          "//result/doc[4]/int[@name='id'][.=7]"
+          );
+
+      // NEGATIVE_INF to Negative
+      assertQ(req("q", fieldName[i]+":[* TO -1]", "sort", "id asc", "fl", "id,"+fieldName[i]+",score"),
+          "//*[@numFound='2']",
+          "//result/doc[1]/int[@name='id'][.=2]",
+          "//result/doc[2]/int[@name='id'][.=6]"
+          );
+
+      // NEGATIVE_INF to Positive
+      assertQ(req("q", fieldName[i]+":[* TO 2]", "sort", "id asc", "fl", "id,"+fieldName[i]),
+          "//*[@numFound='4']",
+          "//result/doc[1]/int[@name='id'][.=1]",
+          "//result/doc[2]/int[@name='id'][.=2]",
+          "//result/doc[3]/int[@name='id'][.=5]",
+          "//result/doc[4]/int[@name='id'][.=6]"
+          );
+
+      // NEGATIVE_INF to Positive (non-inclusive)
+      assertQ(req("q", fieldName[i]+":[* TO 2}", "sort", "id asc", "fl", "id,"+fieldName[i]),
+          "//*[@numFound='3']",
+          "//result/doc[1]/int[@name='id'][.=2]",
+          "//result/doc[2]/int[@name='id'][.=5]",
+          "//result/doc[3]/int[@name='id'][.=6]"
+          );
+
+      // Negative to POSITIVE_INF
+      assertQ(req("q", fieldName[i]+":[-6 TO *]", "sort", "id asc", "fl", "id,"+fieldName[i]),
+          "//*[@numFound='6']",
+          "//result/doc[1]/int[@name='id'][.=1]",
+          "//result/doc[2]/int[@name='id'][.=2]",
+          "//result/doc[3]/int[@name='id'][.=3]",
+          "//result/doc[4]/int[@name='id'][.=4]",
+          "//result/doc[5]/int[@name='id'][.=5]",
+          "//result/doc[6]/int[@name='id'][.=7]"
+          );
+
+      // NEGATIVE_INF to POSITIVE_INF
+      assertQ(req("q", fieldName[i]+":[* TO *]", "sort", "id asc", "fl", "id,"+fieldName[i]+",score"),
+          "//*[@numFound='7']",
+          "//result/doc[1]/int[@name='id'][.=1]",
+          "//result/doc[2]/int[@name='id'][.=2]",
+          "//result/doc[3]/int[@name='id'][.=3]",
+          "//result/doc[4]/int[@name='id'][.=4]",
+          "//result/doc[5]/int[@name='id'][.=5]",
+          "//result/doc[6]/int[@name='id'][.=6]",
+          "//result/doc[7]/int[@name='id'][.=7]",
+          "//result/doc[1]/float[@name='score'][.=1.0]",
+          "//result/doc[2]/float[@name='score'][.=1.0]",
+          "//result/doc[3]/float[@name='score'][.=1.0]",
+          "//result/doc[4]/float[@name='score'][.=1.0]",
+          "//result/doc[5]/float[@name='score'][.=1.0]",
+          "//result/doc[6]/float[@name='score'][.=1.0]",
+          "//result/doc[7]/float[@name='score'][.=1.0]"
+          );
+    }
   }
 }