SOLR-2522: new two argument option for the existing field() function; picks the min/max value of a docValues field to use as a ValueSource: "field(field_name,min)" and "field(field_name,max)"

git-svn-id: https://svn.apache.org/repos/asf/lucene/dev/trunk@1693625 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Chris M. Hostetter 2015-07-31 16:21:44 +00:00
parent 22d67a637a
commit 5f5ab2a79f
12 changed files with 775 additions and 2 deletions

View File

@ -77,7 +77,7 @@ public class SortedSetSelector {
return new MinValue(sortedSet);
} else {
if (sortedSet instanceof RandomAccessOrds == false) {
throw new UnsupportedOperationException("codec does not support random access ordinals, cannot use selector: " + selector);
throw new UnsupportedOperationException("codec does not support random access ordinals, cannot use selector: " + selector + " docValsImpl: " + sortedSet.toString());
}
RandomAccessOrds randomOrds = (RandomAccessOrds) sortedSet;
switch(selector) {

View File

@ -166,6 +166,9 @@ New Features
* SOLR-7742: Support for Immutable ConfigSets (Gregory Chanan)
* SOLR-2522: new two argument option for the existing field() function; picks the min/max value of a
docValues field to use as a ValueSource: "field(field_name,min)" and "field(field_name,max)" (hossman)
Bug Fixes
----------------------

View File

@ -23,6 +23,7 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
@ -44,6 +45,8 @@ import org.apache.lucene.search.MultiTermQuery;
import org.apache.lucene.search.PrefixQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.SortField;
import org.apache.lucene.search.SortedSetSelector;
import org.apache.lucene.search.SortedNumericSelector;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.search.TermRangeQuery;
import org.apache.lucene.search.similarities.Similarity;
@ -669,6 +672,27 @@ public abstract class FieldType extends FieldProperties {
return new StrFieldSource(field.name);
}
/**
* Method for dynamically building a ValueSource based on a single value of a multivalued field.
*
* The default implementation throws an error except in the trivial case where this method is used on
* a {@link SchemaField} that is in fact not-multivalued, in which case it delegates to
* {@link #getValueSource}
*
* @see MultiValueSelector
*/
public ValueSource getSingleValueSource(MultiValueSelector choice, SchemaField field, QParser parser) {
// trivial base case
if (!field.multiValued()) {
// single value matches any selector
return getValueSource(field, parser);
}
throw new SolrException(ErrorCode.BAD_REQUEST, "Selecting a single value from a multivalued field is not supported for this field: " + field.getName() + " (type: " + this.getTypeName() + ")");
}
/**
* Returns a Query instance for doing range searches on this field type. {@link org.apache.solr.search.SolrQueryParser}
* currently passes part1 and part2 as null if they are '*' respectively. minInclusive and maxInclusive are both true
@ -1028,4 +1052,65 @@ public abstract class FieldType extends FieldProperties {
final byte[] bytes = Base64.base64ToByteArray(val);
return new BytesRef(bytes);
}
/**
* An enumeration representing various options that may exist for selecting a single value from a
* multivalued field. This class is designed to be an abstract representation, agnostic of some of
* the underlying specifics. Not all enum value are garunteeded work in all contexts -- null checks
* must be dont by the caller for the specific methods needed.
*
* @see FieldType#getSingleValueSource
*/
public enum MultiValueSelector {
// trying to be agnostic about SortedSetSelector.Type vs SortedNumericSelector.Type
MIN(SortedSetSelector.Type.MIN, SortedNumericSelector.Type.MIN),
MAX(SortedSetSelector.Type.MAX, SortedNumericSelector.Type.MAX);
@Override
public String toString() { return super.toString().toLowerCase(Locale.ROOT); }
/**
* The appropriate <code>SortedSetSelector.Type</code> option for this <code>MultiValueSelector</code>,
* may be null if there is no equivilent
*/
public SortedSetSelector.Type getSortedSetSelectorType() {
return sType;
}
/**
* The appropriate <code>SortedNumericSelector.Type</code> option for this <code>MultiValueSelector</code>,
* may be null if there is no equivilent
*/
public SortedNumericSelector.Type getSortedNumericSelectorType() {
return nType;
}
private final SortedSetSelector.Type sType;
private final SortedNumericSelector.Type nType;
private MultiValueSelector(SortedSetSelector.Type sType, SortedNumericSelector.Type nType) {
this.sType = sType;
this.nType = nType;
}
/**
* Returns a MultiValueSelector matching the specified (case insensitive) label, or null if
* no corrisponding MultiValueSelector exists.
*
* @param label a non null label to be checked for a corrisponding MultiValueSelector
* @return a MultiValueSelector or null if no MultiValueSelector matches the specified label
*/
public static MultiValueSelector lookup(String label) {
if (null == label) {
throw new NullPointerException("label must not be null when calling MultiValueSelector.lookup");
}
try {
return valueOf(label.toUpperCase(Locale.ROOT));
} catch (IllegalArgumentException e) {
return null;
}
}
}
}

View File

@ -17,6 +17,23 @@
package org.apache.solr.schema;
import java.io.IOException;
import java.util.Map;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.index.DocValues;
import org.apache.lucene.index.SortedDocValues;
import org.apache.lucene.index.SortedSetDocValues;
import org.apache.lucene.queries.function.ValueSource;
import org.apache.lucene.queries.function.FunctionValues;
import org.apache.lucene.queries.function.docvalues.DoubleDocValues;
import org.apache.lucene.queries.function.valuesource.SortedSetFieldSource;
import org.apache.lucene.search.SortedSetSelector;
import org.apache.lucene.util.BytesRef;
import org.apache.lucene.util.NumericUtils;
import org.apache.lucene.util.mutable.MutableValue;
import org.apache.lucene.util.mutable.MutableValueDouble;
/**
* A numeric field that can contain double-precision 64-bit IEEE 754 floating
* point values.
@ -37,4 +54,49 @@ public class TrieDoubleField extends TrieField implements DoubleValueFieldType {
{
type=TrieTypes.DOUBLE;
}
@Override
protected ValueSource getSingleValueSource(SortedSetSelector.Type choice, SchemaField f) {
return new SortedSetFieldSource(f.getName(), choice) {
@Override
public FunctionValues getValues(Map context, LeafReaderContext readerContext) throws IOException {
SortedSetFieldSource thisAsSortedSetFieldSource = this; // needed for nested anon class ref
SortedSetDocValues sortedSet = DocValues.getSortedSet(readerContext.reader(), field);
SortedDocValues view = SortedSetSelector.wrap(sortedSet, selector);
return new DoubleDocValues(thisAsSortedSetFieldSource) {
@Override
public double doubleVal(int doc) {
BytesRef bytes = view.get(doc);
return NumericUtils.sortableLongToDouble(NumericUtils.prefixCodedToLong(bytes));
}
@Override
public boolean exists(int doc) {
return -1 != view.getOrd(doc);
}
@Override
public ValueFiller getValueFiller() {
return new ValueFiller() {
private final MutableValueDouble mval = new MutableValueDouble();
@Override
public MutableValue getValue() {
return mval;
}
@Override
public void fillValue(int doc) {
mval.exists = exists(doc);
mval.value = mval.exists ? doubleVal(doc) : 0.0D;
}
};
}
};
}
};
}
}

View File

@ -42,6 +42,7 @@ import org.apache.lucene.queries.function.valuesource.LongFieldSource;
import org.apache.lucene.search.DocValuesRangeQuery;
import org.apache.lucene.search.NumericRangeQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.SortedSetSelector;
import org.apache.lucene.search.SortField;
import org.apache.lucene.uninverting.UninvertingReader.Type;
import org.apache.lucene.util.BytesRef;
@ -244,7 +245,50 @@ public class TrieField extends PrimitiveFieldType {
}
}
@Override
public final ValueSource getSingleValueSource(MultiValueSelector choice, SchemaField field, QParser parser) {
// trivial base case
if (!field.multiValued()) {
// single value matches any selector
return getValueSource(field, parser);
}
// See LUCENE-6709
if (! field.hasDocValues()) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
"docValues='true' is required to select '" + choice.toString() +
"' value from multivalued field ("+ field.getName() +") at query time");
}
// multivalued Trie fields all use SortedSetDocValues, so we give a clean error if that's
// not supported by the specified choice, else we delegate to a helper
SortedSetSelector.Type selectorType = choice.getSortedSetSelectorType();
if (null == selectorType) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
choice.toString() + " is not a supported option for picking a single value"
+ " from the multivalued field: " + field.getName() +
" (type: " + this.getTypeName() + ")");
}
return getSingleValueSource(selectorType, field);
}
/**
* Helper method that will only be called for multivalued Trie fields that have doc values.
* Default impl throws an error indicating that selecting a single value from this multivalued
* field is not supported for this field type
*
* @param choice the selector Type to use, will never be null
* @param field the field to use, garunteed to be multivalued.
* @see #getSingleValueSource(MultiValueSelector,SchemaField,QParser)
*/
protected ValueSource getSingleValueSource(SortedSetSelector.Type choice, SchemaField field) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
"Can not select a single value for multivalued field: " + field.getName()
+ " (single valued field selection not supported for type: " + this.getTypeName()
+ ")");
}
@Override
public void write(TextResponseWriter writer, String name, StorableField f) throws IOException {
writer.writeVal(name, toObject(f));

View File

@ -17,6 +17,23 @@
package org.apache.solr.schema;
import java.io.IOException;
import java.util.Map;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.index.DocValues;
import org.apache.lucene.index.SortedDocValues;
import org.apache.lucene.index.SortedSetDocValues;
import org.apache.lucene.queries.function.ValueSource;
import org.apache.lucene.queries.function.FunctionValues;
import org.apache.lucene.queries.function.docvalues.FloatDocValues;
import org.apache.lucene.queries.function.valuesource.SortedSetFieldSource;
import org.apache.lucene.search.SortedSetSelector;
import org.apache.lucene.util.BytesRef;
import org.apache.lucene.util.NumericUtils;
import org.apache.lucene.util.mutable.MutableValue;
import org.apache.lucene.util.mutable.MutableValueFloat;
/**
* A numeric field that can contain single-precision 32-bit IEEE 754
* floating point values.
@ -45,4 +62,50 @@ public class TrieFloatField extends TrieField implements FloatValueFieldType {
if (val instanceof String) return Float.parseFloat((String) val);
return super.toNativeType(val);
}
@Override
protected ValueSource getSingleValueSource(SortedSetSelector.Type choice, SchemaField f) {
return new SortedSetFieldSource(f.getName(), choice) {
@Override
public FunctionValues getValues(Map context, LeafReaderContext readerContext) throws IOException {
SortedSetFieldSource thisAsSortedSetFieldSource = this; // needed for nested anon class ref
SortedSetDocValues sortedSet = DocValues.getSortedSet(readerContext.reader(), field);
SortedDocValues view = SortedSetSelector.wrap(sortedSet, selector);
return new FloatDocValues(thisAsSortedSetFieldSource) {
@Override
public float floatVal(int doc) {
BytesRef bytes = view.get(doc);
return NumericUtils.sortableIntToFloat(NumericUtils.prefixCodedToInt(bytes));
}
@Override
public boolean exists(int doc) {
return -1 != view.getOrd(doc);
}
@Override
public ValueFiller getValueFiller() {
return new ValueFiller() {
private final MutableValueFloat mval = new MutableValueFloat();
@Override
public MutableValue getValue() {
return mval;
}
@Override
public void fillValue(int doc) {
mval.exists = exists(doc);
mval.value = mval.exists ? floatVal(doc) : 0.0F;
}
};
}
};
}
};
}
}

View File

@ -17,6 +17,23 @@
package org.apache.solr.schema;
import java.io.IOException;
import java.util.Map;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.index.DocValues;
import org.apache.lucene.index.SortedDocValues;
import org.apache.lucene.index.SortedSetDocValues;
import org.apache.lucene.queries.function.ValueSource;
import org.apache.lucene.queries.function.FunctionValues;
import org.apache.lucene.queries.function.docvalues.IntDocValues;
import org.apache.lucene.queries.function.valuesource.SortedSetFieldSource;
import org.apache.lucene.search.SortedSetSelector;
import org.apache.lucene.util.BytesRef;
import org.apache.lucene.util.NumericUtils;
import org.apache.lucene.util.mutable.MutableValue;
import org.apache.lucene.util.mutable.MutableValueInt;
/**
* A numeric field that can contain 32-bit signed two's complement integer values.
*
@ -44,4 +61,49 @@ public class TrieIntField extends TrieField implements IntValueFieldType {
}
return super.toNativeType(val);
}
@Override
protected ValueSource getSingleValueSource(SortedSetSelector.Type choice, SchemaField f) {
return new SortedSetFieldSource(f.getName(), choice) {
@Override
public FunctionValues getValues(Map context, LeafReaderContext readerContext) throws IOException {
SortedSetFieldSource thisAsSortedSetFieldSource = this; // needed for nested anon class ref
SortedSetDocValues sortedSet = DocValues.getSortedSet(readerContext.reader(), field);
SortedDocValues view = SortedSetSelector.wrap(sortedSet, selector);
return new IntDocValues(thisAsSortedSetFieldSource) {
@Override
public int intVal(int doc) {
BytesRef bytes = view.get(doc);
return NumericUtils.prefixCodedToInt(bytes);
}
@Override
public boolean exists(int doc) {
return -1 != view.getOrd(doc);
}
@Override
public ValueFiller getValueFiller() {
return new ValueFiller() {
private final MutableValueInt mval = new MutableValueInt();
@Override
public MutableValue getValue() {
return mval;
}
@Override
public void fillValue(int doc) {
mval.exists = exists(doc);
mval.value = mval.exists ? intVal(doc) : 0;
}
};
}
};
}
};
}
}

View File

@ -17,6 +17,23 @@
package org.apache.solr.schema;
import java.io.IOException;
import java.util.Map;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.index.DocValues;
import org.apache.lucene.index.SortedDocValues;
import org.apache.lucene.index.SortedSetDocValues;
import org.apache.lucene.queries.function.ValueSource;
import org.apache.lucene.queries.function.FunctionValues;
import org.apache.lucene.queries.function.docvalues.LongDocValues;
import org.apache.lucene.queries.function.valuesource.SortedSetFieldSource;
import org.apache.lucene.search.SortedSetSelector;
import org.apache.lucene.util.BytesRef;
import org.apache.lucene.util.NumericUtils;
import org.apache.lucene.util.mutable.MutableValue;
import org.apache.lucene.util.mutable.MutableValueLong;
/**
* A numeric field that can contain 64-bit signed two's complement integer values.
*
@ -31,4 +48,49 @@ public class TrieLongField extends TrieField implements LongValueFieldType {
{
type=TrieTypes.LONG;
}
@Override
protected ValueSource getSingleValueSource(SortedSetSelector.Type choice, SchemaField f) {
return new SortedSetFieldSource(f.getName(), choice) {
@Override
public FunctionValues getValues(Map context, LeafReaderContext readerContext) throws IOException {
SortedSetFieldSource thisAsSortedSetFieldSource = this; // needed for nested anon class ref
SortedSetDocValues sortedSet = DocValues.getSortedSet(readerContext.reader(), field);
SortedDocValues view = SortedSetSelector.wrap(sortedSet, selector);
return new LongDocValues(thisAsSortedSetFieldSource) {
@Override
public long longVal(int doc) {
BytesRef bytes = view.get(doc);
return NumericUtils.prefixCodedToLong(bytes);
}
@Override
public boolean exists(int doc) {
return -1 != view.getOrd(doc);
}
@Override
public ValueFiller getValueFiller() {
return new ValueFiller() {
private final MutableValueLong mval = new MutableValueLong();
@Override
public MutableValue getValue() {
return mval;
}
@Override
public void fillValue(int doc) {
mval.exists = exists(doc);
mval.value = mval.exists ? longVal(doc) : 0;
}
};
}
};
}
};
}
}

View File

@ -398,6 +398,17 @@ public abstract class ValueSourceParser implements NamedListInitializedPlugin {
String fieldName = fp.parseArg();
SchemaField f = fp.getReq().getSchema().getField(fieldName);
if (fp.hasMoreArguments()) {
// multivalued selector option
String s = fp.parseArg();
FieldType.MultiValueSelector selector = FieldType.MultiValueSelector.lookup(s);
if (null == selector) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
"Multi-Valued field selector '"+s+"' not spported");
}
return f.getType().getSingleValueSource(selector, f, fp);
}
// simple field ValueSource
return f.getType().getValueSource(f, fp);
}
});
@ -563,6 +574,7 @@ public abstract class ValueSourceParser implements NamedListInitializedPlugin {
return new MinFloatFunction(sources.toArray(new ValueSource[sources.size()]));
}
});
addParser("sqedist", new ValueSourceParser() {
@Override
public ValueSource parse(FunctionQParser fp) throws SyntaxError {

View File

@ -506,7 +506,7 @@
<!-- points to the root document of a block of nested documents -->
<field name="_root_" type="string" indexed="true" stored="true"/>
<field name="multi_int_with_docvals" type="tint" multiValued="true" docValues="true" indexed="false" />
<dynamicField name="*_coordinate" type="tdouble" indexed="true" stored="false"/>

View File

@ -817,6 +817,28 @@ public class QueryEqualityTest extends SolrTestCaseJ4 {
assertFuncEquals("field(\"foo_i\")",
"field('foo_i\')",
"foo_i");
// simple VS of single valued field should be same as asking for min/max on that field
assertFuncEquals("field(\"foo_i\")",
"field('foo_i',min)",
"field(foo_i,'min')",
"field('foo_i',max)",
"field(foo_i,'max')",
"foo_i");
// multivalued field with selector
String multif = "multi_int_with_docvals";
SolrQueryRequest req = req("my_field", multif);
// this test is only viable if it's a multivalued field, sanity check the schema
assertTrue(multif + " is no longer multivalued, who broke this schema?",
req.getSchema().getField(multif).multiValued());
assertFuncEquals(req,
"field($my_field,'MIN')",
"field('"+multif+"',min)");
assertFuncEquals(req,
"field($my_field,'max')",
"field('"+multif+"',Max)");
}
public void testFuncCurrency() throws Exception {
assertFuncEquals("currency(\"amount\")",

View File

@ -0,0 +1,358 @@
/*
* 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.search.function;
import org.apache.lucene.util.LuceneTestCase.SuppressCodecs;
import org.apache.lucene.util.TestUtil;
import org.apache.solr.SolrTestCaseJ4;
import org.apache.solr.schema.IndexSchema;
import org.apache.solr.schema.SchemaField;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.SolrInputDocument;
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.common.util.NamedList;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Ignore;
@SuppressCodecs({"Memory", "SimpleText"}) // see TestSortedSetSelector
public class TestMinMaxOnMultiValuedField extends SolrTestCaseJ4 {
/** Initializes core and does some sanity checking of schema */
@BeforeClass
public static void beforeClass() throws Exception {
initCore("solrconfig-functionquery.xml","schema11.xml");
// sanity check the expected properties of our fields (ie: who broke the schema?)
IndexSchema schema = h.getCore().getLatestSchema();
for (String type : new String[] {"i", "l", "f", "d"}) {
for (String suffix : new String [] {"", "_dv", "_ni_dv"}) {
String f = "val_t" + type + "s" + suffix;
SchemaField sf = schema.getField(f);
assertTrue(f + " is not multivalued", sf.multiValued());
assertEquals(f + " doesn't have expected docValues status",
f.contains("dv"), sf.hasDocValues());
assertEquals(f + " doesn't have expected index status",
! f.contains("ni"), sf.indexed());
}
}
}
/** Deletes all docs (which may be left over from a previous test */
@Before
public void before() throws Exception {
assertU(delQ("*:*"));
assertU(commit());
}
public void testBasics() throws Exception {
assertU(adoc(sdoc("id", "1"
// int
,"val_tis_dv", "42"
,"val_tis_dv", "9"
,"val_tis_dv", "-54"
// long
,"val_tls_dv", "420"
,"val_tls_dv", "90"
,"val_tls_dv", "-540"
// float
,"val_tfs_dv", "-42.5"
,"val_tfs_dv", "-4.5"
,"val_tfs_dv", "-13.5"
// double
,"val_tds_dv", "-420.5"
,"val_tds_dv", "-40.5"
,"val_tds_dv", "-130.5"
)));
assertU(commit());
assertQ(req("q","id:1"
// int
,"fl","exists_min_i:exists(field(val_tis_dv,min))"
,"fl","exists_max_i:exists(field(val_tis_dv,max))"
,"fl","min_i:field(val_tis_dv,min)"
,"fl","max_i:field(val_tis_dv,max)"
// long
,"fl","exists_min_l:exists(field(val_tls_dv,min))"
,"fl","exists_max_l:exists(field(val_tls_dv,max))"
,"fl","min_l:field(val_tls_dv,min)"
,"fl","max_l:field(val_tls_dv,max)"
// float
,"fl","exists_min_f:exists(field(val_tfs_dv,min))"
,"fl","exists_max_f:exists(field(val_tfs_dv,max))"
,"fl","min_f:field(val_tfs_dv,min)"
,"fl","max_f:field(val_tfs_dv,max)"
// double
,"fl","exists_min_d:exists(field(val_tds_dv,min))"
,"fl","exists_max_d:exists(field(val_tds_dv,max))"
,"fl","min_d:field(val_tds_dv,min)"
,"fl","max_d:field(val_tds_dv,max)"
)
,"//*[@numFound='1']"
// int
,"//bool[@name='exists_min_i']='true'"
,"//bool[@name='exists_max_i']='true'"
,"//int[@name='min_i']='-54'"
,"//int[@name='max_i']='42'"
// long
,"//bool[@name='exists_min_l']='true'"
,"//bool[@name='exists_max_l']='true'"
,"//long[@name='min_l']='-540'"
,"//long[@name='max_l']='420'"
// float
,"//bool[@name='exists_min_f']='true'"
,"//bool[@name='exists_max_f']='true'"
,"//float[@name='min_f']='-42.5'"
,"//float[@name='max_f']='-4.5'"
// double
,"//bool[@name='exists_min_d']='true'"
,"//bool[@name='exists_max_d']='true'"
,"//double[@name='min_d']='-420.5'"
,"//double[@name='max_d']='-40.5'"
);
}
@AwaitsFix(bugUrl = "https://issues.apache.org/jira/browse/LUCENE-6709")
public void testIntFieldCache() {
testSimpleInt("val_tis");
}
public void testIntDocValues() {
testSimpleInt("val_tis_dv");
testSimpleInt("val_tis_ni_dv");
}
@AwaitsFix(bugUrl = "https://issues.apache.org/jira/browse/LUCENE-6709")
public void testLongFieldCache() {
testSimpleLong("val_tls");
}
public void testLongDocValues() {
testSimpleLong("val_tls_dv");
testSimpleLong("val_tls_ni_dv");
}
@AwaitsFix(bugUrl = "https://issues.apache.org/jira/browse/LUCENE-6709")
public void testFloatFieldCache() {
testSimpleFloat("val_tfs");
}
public void testFloatDocValues() {
testSimpleFloat("val_tfs_dv");
testSimpleFloat("val_tfs_ni_dv");
}
@AwaitsFix(bugUrl = "https://issues.apache.org/jira/browse/LUCENE-6709")
public void testDoubleFieldCache() {
testSimpleDouble("val_tds");
}
public void testDoubleDocValues() {
testSimpleDouble("val_tds_dv");
testSimpleDouble("val_tds_ni_dv");
}
public void testBadRequests() {
// useful error msg when bogus selector is requested (ie: not min or max)
assertQEx("no error asking for bogus selector",
"hoss",
req("q","*:*", "fl", "field(val_tds_dv,'hoss')"),
SolrException.ErrorCode.BAD_REQUEST);
// useful error until/unless LUCENE-6709
assertQEx("no error asking for max on a non docVals field",
"val_tds",
req("q","*:*", "fl", "field(val_tds,'max')"),
SolrException.ErrorCode.BAD_REQUEST);
assertQEx("no error asking for max on a non docVals field",
"max",
req("q","*:*", "fl", "field(val_tds,'max')"),
SolrException.ErrorCode.BAD_REQUEST);
assertQEx("no error asking for max on a non docVals field",
"docValues",
req("q","*:*", "fl", "field(val_tds,'max')"),
SolrException.ErrorCode.BAD_REQUEST);
// useful error if min/max is unsupported for fieldtype
assertQEx("no error asking for max on a str field",
"cat_docValues",
req("q","*:*", "fl", "field(cat_docValues,'max')"),
SolrException.ErrorCode.BAD_REQUEST);
assertQEx("no error asking for max on a str field",
"string",
req("q","*:*", "fl", "field(cat_docValues,'max')"),
SolrException.ErrorCode.BAD_REQUEST);
}
public void testRandom() throws Exception {
Comparable[] vals = new Comparable[TestUtil.nextInt(random(), 1, 17)];
// random ints
for (int i = 0; i < vals.length; i++) {
vals[i] = random().nextInt();
}
testSimpleValues("val_tis_dv", int.class, vals);
// random longs
for (int i = 0; i < vals.length; i++) {
vals[i] = random().nextLong();
}
testSimpleValues("val_tls_dv", long.class, vals);
// random floats
for (int i = 0; i < vals.length; i++) {
// Random.nextFloat is lame
Float f = Float.NaN;
while (f.isNaN()) {
f = Float.intBitsToFloat(random().nextInt());
}
vals[i] = f;
}
testSimpleValues("val_tfs_dv", float.class, vals);
// random doubles
for (int i = 0; i < vals.length; i++) {
// Random.nextDouble is lame
Double d = Double.NaN;
while (d.isNaN()) {
d = Double.longBitsToDouble(random().nextLong());
}
vals[i] = d;
}
testSimpleValues("val_tds_dv", double.class, vals);
}
/** @see #testSimpleValues */
protected void testSimpleInt(final String fieldname) {
// most basic case
testSimpleValues(fieldname, int.class, 0);
// order of values shouldn't matter
testSimpleValues(fieldname, int.class, -42, 51, 3);
testSimpleValues(fieldname, int.class, 51, 3, -42);
// extreme's of the data type
testSimpleValues(fieldname, int.class, Integer.MIN_VALUE, 42, -550);
testSimpleValues(fieldname, int.class, Integer.MAX_VALUE, 0, Integer.MIN_VALUE);
}
/** @see #testSimpleValues */
protected void testSimpleLong(final String fieldname) {
// most basic case
testSimpleValues(fieldname, long.class, 0);
// order of values shouldn't matter
testSimpleValues(fieldname, long.class, -42L, 51L, 3L);
testSimpleValues(fieldname, long.class, 51L, 3L, -42L);
// extreme's of the data type
testSimpleValues(fieldname, long.class, Long.MIN_VALUE, 42L, -550L);
testSimpleValues(fieldname, long.class, Long.MAX_VALUE, 0L, Long.MIN_VALUE);
}
/** @see #testSimpleValues */
protected void testSimpleFloat(final String fieldname) {
// most basic case
testSimpleValues(fieldname, float.class, 0.0F);
// order of values shouldn't matter
testSimpleValues(fieldname, float.class, -42.5F, 51.3F, 3.1415F);
testSimpleValues(fieldname, float.class, 51.3F, 3.1415F, -42.5F);
// extreme's of the data type
testSimpleValues(fieldname, float.class, Float.NEGATIVE_INFINITY, 42.5F, -550.4F);
testSimpleValues(fieldname, float.class, Float.POSITIVE_INFINITY, 0.0F, Float.NEGATIVE_INFINITY);
}
/** @see #testSimpleValues */
protected void testSimpleDouble(final String fieldname) {
// most basic case
testSimpleValues(fieldname, double.class, 0.0D);
// order of values shouldn't matter
testSimpleValues(fieldname, double.class, -42.5D, 51.3D, 3.1415D);
testSimpleValues(fieldname, double.class, 51.3D, 3.1415D, -42.5D);
// extreme's of the data type
testSimpleValues(fieldname, double.class, Double.NEGATIVE_INFINITY, 42.5D, -550.4D);
testSimpleValues(fieldname, double.class, Double.POSITIVE_INFINITY, 0.0D, Double.NEGATIVE_INFINITY);
}
/** Tests a single doc with a few explicit values, as well as testing exists with and w/o values */
protected void testSimpleValues(final String fieldname, final Class clazz, final Comparable... vals) {
assert 0 < vals.length;
Comparable min = vals[0];
Comparable max = vals[0];
final String type = clazz.getName();
final SolrInputDocument doc1 = sdoc("id", "1");
for (Comparable v : vals) {
doc1.addField(fieldname, v);
if (0 < min.compareTo(v)) {
min = v;
}
if (0 > max.compareTo(v)) {
max = v;
}
}
assertU(adoc(doc1));
assertU(adoc(sdoc("id", "2"))); // fieldname doesn't exist
assertU(commit());
assertQ(fieldname,
req("q","id:1",
"fl","exists_val_min:exists(field("+fieldname+",min))",
"fl","exists_val_max:exists(field("+fieldname+",max))",
"fl","val_min:field("+fieldname+",min)",
"fl","val_max:field("+fieldname+",max)")
,"//*[@numFound='1']"
,"//bool[@name='exists_val_min']='true'"
,"//bool[@name='exists_val_max']='true'"
,"//"+type+"[@name='val_min']='"+min+"'"
,"//"+type+"[@name='val_max']='"+max+"'"
);
assertQ(fieldname,
req("q","id:2",
"fl","exists_val_min:exists(field("+fieldname+",min))",
"fl","exists_val_max:exists(field("+fieldname+",max))",
"fl","val_min:field("+fieldname+",min)",
"fl","val_max:field("+fieldname+",max)")
,"//*[@numFound='1']"
,"//bool[@name='exists_val_min']='false'"
,"//bool[@name='exists_val_max']='false'"
,"count(//"+type+"[@name='val_min'])=0"
,"count(//"+type+"[@name='val_max'])=0"
);
}
}