mirror of https://github.com/apache/lucene.git
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:
parent
22d67a637a
commit
5f5ab2a79f
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
----------------------
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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"/>
|
||||
|
||||
|
|
|
@ -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\")",
|
||||
|
|
|
@ -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"
|
||||
);
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue