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:43 UTC
lucene-solr:branch_6_0: 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_6_0 a600e6f2d -> c3d0276b2
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/c3d0276b
Tree: http://git-wip-us.apache.org/repos/asf/lucene-solr/tree/c3d0276b
Diff: http://git-wip-us.apache.org/repos/asf/lucene-solr/diff/c3d0276b
Branch: refs/heads/branch_6_0
Commit: c3d0276b2f50ca6c1b8dd1298fc2e214c4020dbf
Parents: a600e6f
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:57:31 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/c3d0276b/solr/CHANGES.txt
----------------------------------------------------------------------
diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index 9932447..827451e 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -305,6 +305,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/c3d0276b/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/c3d0276b/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/c3d0276b/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/c3d0276b/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/c3d0276b/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]"
+ );
+ }
}
}