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

This commit is contained in:
Steve Rowe 2016-03-20 16:55:12 -04:00
parent 7f9c4d886d
commit 49d5ec02a2
6 changed files with 491 additions and 67 deletions

View File

@ -366,6 +366,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
----------------------

View File

@ -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()) {

View File

@ -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() {

View File

@ -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" />

View File

@ -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='2']",
"//result/doc[1]/str[@name='id'][.=1]",
"//result/doc[2]/str[@name='id'][.=3]"
"//*[@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'][.=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() {

View File

@ -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]"
);
}
}
}