/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.solr.schema; import java.io.IOException; import java.lang.invoke.MethodHandles; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.List; import java.util.function.Function; import java.util.function.Supplier; import org.apache.lucene.index.DocValuesType; import org.apache.lucene.index.FieldInfos; import org.apache.lucene.index.IndexableField; import org.apache.lucene.index.LeafReader; import org.apache.lucene.index.NumericDocValues; import org.apache.lucene.index.SortedDocValues; 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; import org.apache.solr.util.RefCounted; import org.junit.BeforeClass; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class DocValuesTest extends SolrTestCaseJ4 { private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); @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", "booldv"}) { 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 { super.setUp(); assertU(delQ("*:*")); } @Test public void testDocValues() throws IOException { assertU(adoc("id", "1")); assertU(commit()); try (SolrCore core = h.getCoreInc()) { final RefCounted<SolrIndexSearcher> searcherRef = core.openNewSearcher(true, true); final SolrIndexSearcher searcher = searcherRef.get(); try { final LeafReader reader = searcher.getSlowAtomicReader(); assertEquals(1, reader.numDocs()); final FieldInfos infos = reader.getFieldInfos(); assertEquals(DocValuesType.NUMERIC, infos.fieldInfo("floatdv").getDocValuesType()); assertEquals(DocValuesType.NUMERIC, infos.fieldInfo("intdv").getDocValuesType()); assertEquals(DocValuesType.NUMERIC, infos.fieldInfo("doubledv").getDocValuesType()); assertEquals(DocValuesType.NUMERIC, infos.fieldInfo("longdv").getDocValuesType()); assertEquals(DocValuesType.SORTED, infos.fieldInfo("stringdv").getDocValuesType()); assertEquals(DocValuesType.SORTED, infos.fieldInfo("booldv").getDocValuesType()); NumericDocValues dvs = reader.getNumericDocValues("floatdv"); assertEquals(0, dvs.nextDoc()); assertEquals((long) Float.floatToIntBits(1), dvs.longValue()); dvs = reader.getNumericDocValues("intdv"); assertEquals(0, dvs.nextDoc()); assertEquals(2L, dvs.longValue()); dvs = reader.getNumericDocValues("doubledv"); assertEquals(0, dvs.nextDoc()); assertEquals(Double.doubleToLongBits(3), dvs.longValue()); dvs = reader.getNumericDocValues("longdv"); assertEquals(0, dvs.nextDoc()); assertEquals(4L, dvs.longValue()); SortedDocValues sdv = reader.getSortedDocValues("stringdv"); assertEquals(0, sdv.nextDoc()); assertEquals("solr", sdv.binaryValue().utf8ToString()); sdv = reader.getSortedDocValues("booldv"); assertEquals(0, sdv.nextDoc()); assertEquals("T", sdv.binaryValue().utf8ToString()); final IndexSchema schema = core.getLatestSchema(); final SchemaField floatDv = schema.getField("floatdv"); final SchemaField intDv = schema.getField("intdv"); final SchemaField doubleDv = schema.getField("doubledv"); final SchemaField longDv = schema.getField("longdv"); final SchemaField boolDv = schema.getField("booldv"); FunctionValues values = floatDv.getType().getValueSource(floatDv, null).getValues(null, searcher.getSlowAtomicReader().leaves().get(0)); assertEquals(1f, values.floatVal(0), 0f); assertEquals(1f, values.objectVal(0)); values = intDv.getType().getValueSource(intDv, null).getValues(null, searcher.getSlowAtomicReader().leaves().get(0)); assertEquals(2, values.intVal(0)); assertEquals(2, values.objectVal(0)); values = doubleDv.getType().getValueSource(doubleDv, null).getValues(null, searcher.getSlowAtomicReader().leaves().get(0)); assertEquals(3d, values.doubleVal(0), 0d); assertEquals(3d, values.objectVal(0)); values = longDv.getType().getValueSource(longDv, null).getValues(null, searcher.getSlowAtomicReader().leaves().get(0)); assertEquals(4L, values.longVal(0)); assertEquals(4L, values.objectVal(0)); values = boolDv.getType().getValueSource(boolDv, null).getValues(null, searcher.getSlowAtomicReader().leaves().get(0)); assertEquals("true", values.strVal(0)); assertEquals(true, values.objectVal(0)); // check reversibility of created fields tstToObj(schema.getField("floatdv"), -1.5f); tstToObj(schema.getField("floatdvs"), -1.5f); tstToObj(schema.getField("doubledv"), -1.5d); tstToObj(schema.getField("doubledvs"), -1.5d); tstToObj(schema.getField("intdv"), -7); tstToObj(schema.getField("intdvs"), -7); tstToObj(schema.getField("longdv"), -11L); tstToObj(schema.getField("longdvs"), -11L); tstToObj(schema.getField("datedv"), new Date(1000)); tstToObj(schema.getField("datedvs"), new Date(1000)); tstToObj(schema.getField("stringdv"), "foo"); tstToObj(schema.getField("stringdvs"), "foo"); tstToObj(schema.getField("booldv"), true); tstToObj(schema.getField("booldvs"), true); } finally { searcherRef.decref(); } } } private void tstToObj(SchemaField sf, Object o) { List<IndexableField> fields = sf.createFields(o); for (IndexableField field : fields) { assertEquals( sf.getType().toObject(field), o); } } @Test public void testDocValuesSorting() { assertU(adoc("id", "1", "floatdv", "2", "intdv", "3", "doubledv", "4", "longdv", "5", "datedv", "1995-12-31T23:59:59.999Z", "stringdv", "b", "booldv", "true")); assertU(adoc("id", "2", "floatdv", "5", "intdv", "4", "doubledv", "3", "longdv", "2", "datedv", "1997-12-31T23:59:59.999Z", "stringdv", "a", "booldv", "false")); assertU(adoc("id", "3", "floatdv", "3", "intdv", "1", "doubledv", "2", "longdv", "1", "datedv", "1996-12-31T23:59:59.999Z", "stringdv", "c", "booldv", "true")); assertU(adoc("id", "4")); assertU(commit()); assertQ(req("q", "*:*", "sort", "floatdv desc", "rows", "1", "fl", "id"), "//int[@name='id'][.='2']"); assertQ(req("q", "*:*", "sort", "intdv desc", "rows", "1", "fl", "id"), "//int[@name='id'][.='2']"); assertQ(req("q", "*:*", "sort", "doubledv desc", "rows", "1", "fl", "id"), "//int[@name='id'][.='1']"); assertQ(req("q", "*:*", "sort", "longdv desc", "rows", "1", "fl", "id"), "//int[@name='id'][.='1']"); assertQ(req("q", "*:*", "sort", "datedv desc", "rows", "1", "fl", "id,datedv"), "//int[@name='id'][.='2']", "//result/doc[1]/date[@name='datedv'][.='1997-12-31T23:59:59.999Z']" ); assertQ(req("q", "*:*", "sort", "stringdv desc", "rows", "1", "fl", "id"), "//int[@name='id'][.='4']"); assertQ(req("q", "*:*", "sort", "floatdv asc", "rows", "1", "fl", "id"), "//int[@name='id'][.='4']"); assertQ(req("q", "*:*", "sort", "intdv asc", "rows", "1", "fl", "id"), "//int[@name='id'][.='3']"); assertQ(req("q", "*:*", "sort", "doubledv asc", "rows", "1", "fl", "id"), "//int[@name='id'][.='3']"); assertQ(req("q", "*:*", "sort", "longdv asc", "rows", "1", "fl", "id"), "//int[@name='id'][.='3']"); assertQ(req("q", "*:*", "sort", "datedv asc", "rows", "1", "fl", "id"), "//int[@name='id'][.='1']"); assertQ(req("q", "*:*", "sort", "stringdv asc", "rows", "1", "fl", "id"), "//int[@name='id'][.='2']"); assertQ(req("q", "*:*", "sort", "booldv asc", "rows", "10", "fl", "booldv,stringdv"), "//result/doc[1]/bool[@name='booldv'][.='false']", "//result/doc[2]/bool[@name='booldv'][.='true']", "//result/doc[3]/bool[@name='booldv'][.='true']", "//result/doc[4]/bool[@name='booldv'][.='true']" ); } @Test public void testDocValuesSorting2() { assertU(adoc("id", "1", "doubledv", "12")); 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.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]/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']" ); } @Test public void testDocValuesFaceting() { for (int i = 0; i < 50; ++i) { assertU(adoc("id", "" + i)); } for (int i = 0; i < 50; ++i) { if (rarely()) { assertU(commit()); // to have several segments } switch (i % 3) { case 0: assertU(adoc("id", "1000" + i, "floatdv", "" + i, "intdv", "" + i, "doubledv", "" + i, "longdv", "" + i, "datedv", (1900 + i) + "-12-31T23:59:59.999Z", "stringdv", "abc" + i, "booldv", "false")); break; case 1: assertU(adoc("id", "1000" + i, "floatdv", "" + i, "intdv", "" + i, "doubledv", "" + i, "longdv", "" + i, "datedv", (1900 + i) + "-12-31T23:59:59.999Z", "stringdv", "abc" + i, "booldv", "true")); break; case 2: assertU(adoc("id", "1000" + i, "floatdv", "" + i, "intdv", "" + i, "doubledv", "" + i, "longdv", "" + i, "datedv", (1900 + i) + "-12-31T23:59:59.999Z", "stringdv", "abc" + i)); break; } } assertU(commit()); assertQ(req("q", "*:*", "facet", "true", "rows", "0", "facet.field", "longdv", "facet.sort", "count", "facet.limit", "1"), "//lst[@name='longdv']/int[@name='4'][.='51']"); assertQ(req("q", "*:*", "facet", "true", "rows", "0", "facet.field", "longdv", "facet.sort", "count", "facet.offset", "1", "facet.limit", "1"), "//lst[@name='longdv']/int[@name='0'][.='1']"); assertQ(req("q", "*:*", "facet", "true", "rows", "0", "facet.field", "longdv", "facet.sort", "index", "facet.offset", "33", "facet.limit", "1", "facet.mincount", "1"), "//lst[@name='longdv']/int[@name='33'][.='1']"); assertQ(req("q", "*:*", "facet", "true", "rows", "0", "facet.field", "floatdv", "facet.sort", "count", "facet.limit", "1"), "//lst[@name='floatdv']/int[@name='1.0'][.='51']"); assertQ(req("q", "*:*", "facet", "true", "rows", "0", "facet.field", "floatdv", "facet.sort", "count", "facet.offset", "1", "facet.limit", "-1", "facet.mincount", "1"), "//lst[@name='floatdv']/int[@name='0.0'][.='1']"); assertQ(req("q", "*:*", "facet", "true", "rows", "0", "facet.field", "floatdv", "facet.sort", "index", "facet.offset", "33", "facet.limit", "1", "facet.mincount", "1"), "//lst[@name='floatdv']/int[@name='33.0'][.='1']"); assertQ(req("q", "*:*", "facet", "true", "rows", "0", "facet.field", "doubledv", "facet.sort", "count", "facet.limit", "1"), "//lst[@name='doubledv']/int[@name='3.0'][.='51']"); assertQ(req("q", "*:*", "facet", "true", "rows", "0", "facet.field", "doubledv", "facet.sort", "count", "facet.offset", "1", "facet.limit", "-1", "facet.mincount", "1"), "//lst[@name='doubledv']/int[@name='0.0'][.='1']"); assertQ(req("q", "*:*", "facet", "true", "rows", "0", "facet.field", "doubledv", "facet.sort", "index", "facet.offset", "33", "facet.limit", "1", "facet.mincount", "1"), "//lst[@name='doubledv']/int[@name='33.0'][.='1']"); assertQ(req("q", "*:*", "facet", "true", "rows", "0", "facet.field", "intdv", "facet.sort", "count", "facet.limit", "1"), "//lst[@name='intdv']/int[@name='2'][.='51']"); assertQ(req("q", "*:*", "facet", "true", "rows", "0", "facet.field", "intdv", "facet.sort", "count", "facet.offset", "1", "facet.limit", "-1", "facet.mincount", "1"), "//lst[@name='intdv']/int[@name='0'][.='1']"); assertQ(req("q", "*:*", "facet", "true", "rows", "0", "facet.field", "intdv", "facet.sort", "index", "facet.offset", "33", "facet.limit", "1", "facet.mincount", "1"), "//lst[@name='intdv']/int[@name='33'][.='1']"); assertQ(req("q", "*:*", "facet", "true", "rows", "0", "facet.field", "datedv", "facet.sort", "count", "facet.limit", "1"), "//lst[@name='datedv']/int[@name='1995-12-31T23:59:59.999Z'][.='50']"); assertQ(req("q", "*:*", "facet", "true", "rows", "0", "facet.field", "datedv", "facet.sort", "count", "facet.offset", "1", "facet.limit", "-1", "facet.mincount", "1"), "//lst[@name='datedv']/int[@name='1900-12-31T23:59:59.999Z'][.='1']"); assertQ(req("q", "*:*", "facet", "true", "rows", "0", "facet.field", "datedv", "facet.sort", "index", "facet.offset", "33", "facet.limit", "1", "facet.mincount", "1"), "//lst[@name='datedv']/int[@name='1933-12-31T23:59:59.999Z'][.='1']"); assertQ(req("q", "booldv:true"), "//*[@numFound='83']"); assertQ(req("q", "booldv:false"), "//*[@numFound='17']"); assertQ(req("q", "*:*", "facet", "true", "rows", "0", "facet.field", "booldv", "facet.sort", "index", "facet.mincount", "1"), "//lst[@name='booldv']/int[@name='false'][.='17']", "//lst[@name='booldv']/int[@name='true'][.='83']"); } @Test public void testDocValuesStats() { for (int i = 0; i < 50; ++i) { assertU(adoc("id", "1000" + i, "floatdv", "" + i%2, "intdv", "" + i%3, "doubledv", "" + i%4, "longdv", "" + i%5, "datedv", (1900+i%6) + "-12-31T23:59:59.999Z", "stringdv", "abc" + i%7)); if (rarely()) { assertU(commit()); // to have several segments } } assertU(commit()); assertQ(req("q", "*:*", "stats", "true", "rows", "0", "stats.field", "stringdv"), "//str[@name='min'][.='abc0']", "//str[@name='max'][.='abc6']", "//long[@name='count'][.='50']"); assertQ(req("q", "*:*", "stats", "true", "rows", "0", "stats.field", "floatdv"), "//double[@name='min'][.='0.0']", "//double[@name='max'][.='1.0']", "//long[@name='count'][.='50']", "//double[@name='sum'][.='25.0']", "//double[@name='mean'][.='0.5']"); assertQ(req("q", "*:*", "stats", "true", "rows", "0", "stats.field", "intdv"), "//double[@name='min'][.='0.0']", "//double[@name='max'][.='2.0']", "//long[@name='count'][.='50']", "//double[@name='sum'][.='49.0']"); assertQ(req("q", "*:*", "stats", "true", "rows", "0", "stats.field", "doubledv"), "//double[@name='min'][.='0.0']", "//double[@name='max'][.='3.0']", "//long[@name='count'][.='50']", "//double[@name='sum'][.='73.0']"); assertQ(req("q", "*:*", "stats", "true", "rows", "0", "stats.field", "longdv"), "//double[@name='min'][.='0.0']", "//double[@name='max'][.='4.0']", "//long[@name='count'][.='50']", "//double[@name='sum'][.='100.0']", "//double[@name='mean'][.='2.0']"); assertQ(req("q", "*:*", "stats", "true", "rows", "0", "stats.field", "datedv"), "//date[@name='min'][.='1900-12-31T23:59:59.999Z']", "//date[@name='max'][.='1905-12-31T23:59:59.999Z']", "//long[@name='count'][.='50']"); assertQ(req("q", "*:*", "stats", "true", "rows", "0", "stats.field", "floatdv", "stats.facet", "intdv"), "//lst[@name='intdv']/lst[@name='0']/long[@name='count'][.='17']", "//lst[@name='intdv']/lst[@name='1']/long[@name='count'][.='17']", "//lst[@name='intdv']/lst[@name='2']/long[@name='count'][.='16']"); assertQ(req("q", "*:*", "stats", "true", "rows", "0", "stats.field", "floatdv", "stats.facet", "datedv"), "//lst[@name='datedv']/lst[@name='1900-12-31T23:59:59.999Z']/long[@name='count'][.='9']", "//lst[@name='datedv']/lst[@name='1901-12-31T23:59:59.999Z']/long[@name='count'][.='9']", "//lst[@name='datedv']/lst[@name='1902-12-31T23:59:59.999Z']/long[@name='count'][.='8']", "//lst[@name='datedv']/lst[@name='1903-12-31T23:59:59.999Z']/long[@name='count'][.='8']", "//lst[@name='datedv']/lst[@name='1904-12-31T23:59:59.999Z']/long[@name='count'][.='8']", "//lst[@name='datedv']/lst[@name='1905-12-31T23:59:59.999Z']/long[@name='count'][.='8']"); } /** Tests the ability to do basic queries (without scoring, just match-only) on * docvalues fields that are not inverted (indexed "forward" only) */ @Test public void testDocValuesMatch() throws Exception { assertU(adoc("id", "1", "floatdv", "2", "intdv", "3", "doubledv", "3.1", "longdv", "5", "datedv", "1995-12-31T23:59:59.999Z", "stringdv", "b", "booldv", "false")); assertU(adoc("id", "2", "floatdv", "-5", "intdv", "4", "doubledv", "-4.3", "longdv", "2", "datedv", "1997-12-31T23:59:59.999Z", "stringdv", "a", "booldv", "true")); assertU(adoc("id", "3", "floatdv", "3", "intdv", "1", "doubledv", "2.1", "longdv", "1", "datedv", "1996-12-31T23:59:59.999Z", "stringdv", "c", "booldv", "false")); 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]/int[@name='id'][.=4]" ); // string: range query assertQ(req("q", "stringdv:[b TO d]", "sort", "id asc"), "//*[@numFound='3']", "//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]/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]/int[@name='id'][.=4]" ); // string: regexp query assertQ(req("q", "stringdv:/c[a-b]r/", "sort", "id asc"), "//*[@numFound='1']", "//result/doc[1]/int[@name='id'][.=4]" ); // float: termquery assertQ(req("q", "floatdv:3", "sort", "id asc"), "//*[@numFound='2']", "//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]/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='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]/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]/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]/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]" ); // boolean basic queries: assertQ(req("q", "booldv:false", "sort", "id asc"), "//*[@numFound='2']", "//result/doc[1]/int[@name='id'][.=1]", "//result/doc[2]/int[@name='id'][.=3]" ); assertQ(req("q", "booldv:true", "sort", "id asc"), "//*[@numFound='2']", "//result/doc[1]/int[@name='id'][.=2]", "//result/doc[2]/int[@name='id'][.=4]" ); } @Test 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); } } } @Test 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]" ); } } }