You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by ho...@apache.org on 2015/07/31 18:21:44 UTC

svn commit: r1693625 - in /lucene/dev/trunk: lucene/core/src/java/org/apache/lucene/search/ solr/ solr/core/src/java/org/apache/solr/schema/ solr/core/src/java/org/apache/solr/search/ solr/core/src/test-files/solr/collection1/conf/ solr/core/src/test/o...

Author: hossman
Date: Fri Jul 31 16:21:44 2015
New Revision: 1693625

URL: http://svn.apache.org/r1693625
Log:
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)"

Added:
    lucene/dev/trunk/solr/core/src/test/org/apache/solr/search/function/TestMinMaxOnMultiValuedField.java   (with props)
Modified:
    lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/search/SortedSetSelector.java
    lucene/dev/trunk/solr/CHANGES.txt
    lucene/dev/trunk/solr/core/src/java/org/apache/solr/schema/FieldType.java
    lucene/dev/trunk/solr/core/src/java/org/apache/solr/schema/TrieDoubleField.java
    lucene/dev/trunk/solr/core/src/java/org/apache/solr/schema/TrieField.java
    lucene/dev/trunk/solr/core/src/java/org/apache/solr/schema/TrieFloatField.java
    lucene/dev/trunk/solr/core/src/java/org/apache/solr/schema/TrieIntField.java
    lucene/dev/trunk/solr/core/src/java/org/apache/solr/schema/TrieLongField.java
    lucene/dev/trunk/solr/core/src/java/org/apache/solr/search/ValueSourceParser.java
    lucene/dev/trunk/solr/core/src/test-files/solr/collection1/conf/schema15.xml
    lucene/dev/trunk/solr/core/src/test/org/apache/solr/search/QueryEqualityTest.java

Modified: lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/search/SortedSetSelector.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/search/SortedSetSelector.java?rev=1693625&r1=1693624&r2=1693625&view=diff
==============================================================================
--- lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/search/SortedSetSelector.java (original)
+++ lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/search/SortedSetSelector.java Fri Jul 31 16:21:44 2015
@@ -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) {

Modified: lucene/dev/trunk/solr/CHANGES.txt
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/CHANGES.txt?rev=1693625&r1=1693624&r2=1693625&view=diff
==============================================================================
--- lucene/dev/trunk/solr/CHANGES.txt (original)
+++ lucene/dev/trunk/solr/CHANGES.txt Fri Jul 31 16:21:44 2015
@@ -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
 ----------------------
 

Modified: lucene/dev/trunk/solr/core/src/java/org/apache/solr/schema/FieldType.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/java/org/apache/solr/schema/FieldType.java?rev=1693625&r1=1693624&r2=1693625&view=diff
==============================================================================
--- lucene/dev/trunk/solr/core/src/java/org/apache/solr/schema/FieldType.java (original)
+++ lucene/dev/trunk/solr/core/src/java/org/apache/solr/schema/FieldType.java Fri Jul 31 16:21:44 2015
@@ -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.MultiTer
 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;
@@ -670,6 +673,27 @@ public abstract class FieldType extends
   }
 
   /**
+   * 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
    * currently by SolrQueryParser but that may change in the future. Also, other QueryParser implementations may have
@@ -1028,4 +1052,65 @@ public abstract class FieldType extends
     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;
+      }
+    }
+
+  }
+  
 }

Modified: lucene/dev/trunk/solr/core/src/java/org/apache/solr/schema/TrieDoubleField.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/java/org/apache/solr/schema/TrieDoubleField.java?rev=1693625&r1=1693624&r2=1693625&view=diff
==============================================================================
--- lucene/dev/trunk/solr/core/src/java/org/apache/solr/schema/TrieDoubleField.java (original)
+++ lucene/dev/trunk/solr/core/src/java/org/apache/solr/schema/TrieDoubleField.java Fri Jul 31 16:21:44 2015
@@ -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 Tri
   {
     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;
+              }
+            };
+          }
+        };
+      }
+    };
+  }
 }

Modified: lucene/dev/trunk/solr/core/src/java/org/apache/solr/schema/TrieField.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/java/org/apache/solr/schema/TrieField.java?rev=1693625&r1=1693624&r2=1693625&view=diff
==============================================================================
--- lucene/dev/trunk/solr/core/src/java/org/apache/solr/schema/TrieField.java (original)
+++ lucene/dev/trunk/solr/core/src/java/org/apache/solr/schema/TrieField.java Fri Jul 31 16:21:44 2015
@@ -42,6 +42,7 @@ import org.apache.lucene.queries.functio
 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 Primitive
     }
   }
 
+  @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));

Modified: lucene/dev/trunk/solr/core/src/java/org/apache/solr/schema/TrieFloatField.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/java/org/apache/solr/schema/TrieFloatField.java?rev=1693625&r1=1693624&r2=1693625&view=diff
==============================================================================
--- lucene/dev/trunk/solr/core/src/java/org/apache/solr/schema/TrieFloatField.java (original)
+++ lucene/dev/trunk/solr/core/src/java/org/apache/solr/schema/TrieFloatField.java Fri Jul 31 16:21:44 2015
@@ -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 Trie
     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;
+              }
+            };
+          }
+        };
+      }
+    };
+  }
+
 }

Modified: lucene/dev/trunk/solr/core/src/java/org/apache/solr/schema/TrieIntField.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/java/org/apache/solr/schema/TrieIntField.java?rev=1693625&r1=1693624&r2=1693625&view=diff
==============================================================================
--- lucene/dev/trunk/solr/core/src/java/org/apache/solr/schema/TrieIntField.java (original)
+++ lucene/dev/trunk/solr/core/src/java/org/apache/solr/schema/TrieIntField.java Fri Jul 31 16:21:44 2015
@@ -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 TrieFi
     }
     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;
+              }
+            };
+          }
+        };
+      }
+    };
+  }
 }

Modified: lucene/dev/trunk/solr/core/src/java/org/apache/solr/schema/TrieLongField.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/java/org/apache/solr/schema/TrieLongField.java?rev=1693625&r1=1693624&r2=1693625&view=diff
==============================================================================
--- lucene/dev/trunk/solr/core/src/java/org/apache/solr/schema/TrieLongField.java (original)
+++ lucene/dev/trunk/solr/core/src/java/org/apache/solr/schema/TrieLongField.java Fri Jul 31 16:21:44 2015
@@ -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 TrieF
   {
     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;
+              }
+            };
+          }
+        };
+      }
+    };
+  }
 }

Modified: lucene/dev/trunk/solr/core/src/java/org/apache/solr/search/ValueSourceParser.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/java/org/apache/solr/search/ValueSourceParser.java?rev=1693625&r1=1693624&r2=1693625&view=diff
==============================================================================
--- lucene/dev/trunk/solr/core/src/java/org/apache/solr/search/ValueSourceParser.java (original)
+++ lucene/dev/trunk/solr/core/src/java/org/apache/solr/search/ValueSourceParser.java Fri Jul 31 16:21:44 2015
@@ -398,6 +398,17 @@ public abstract class ValueSourceParser
 
         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
         return new MinFloatFunction(sources.toArray(new ValueSource[sources.size()]));
       }
     });
+
     addParser("sqedist", new ValueSourceParser() {
       @Override
       public ValueSource parse(FunctionQParser fp) throws SyntaxError {

Modified: lucene/dev/trunk/solr/core/src/test-files/solr/collection1/conf/schema15.xml
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/test-files/solr/collection1/conf/schema15.xml?rev=1693625&r1=1693624&r2=1693625&view=diff
==============================================================================
--- lucene/dev/trunk/solr/core/src/test-files/solr/collection1/conf/schema15.xml (original)
+++ lucene/dev/trunk/solr/core/src/test-files/solr/collection1/conf/schema15.xml Fri Jul 31 16:21:44 2015
@@ -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"/>
 

Modified: lucene/dev/trunk/solr/core/src/test/org/apache/solr/search/QueryEqualityTest.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/test/org/apache/solr/search/QueryEqualityTest.java?rev=1693625&r1=1693624&r2=1693625&view=diff
==============================================================================
--- lucene/dev/trunk/solr/core/src/test/org/apache/solr/search/QueryEqualityTest.java (original)
+++ lucene/dev/trunk/solr/core/src/test/org/apache/solr/search/QueryEqualityTest.java Fri Jul 31 16:21:44 2015
@@ -817,6 +817,28 @@ public class QueryEqualityTest extends S
     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\")", 

Added: lucene/dev/trunk/solr/core/src/test/org/apache/solr/search/function/TestMinMaxOnMultiValuedField.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/test/org/apache/solr/search/function/TestMinMaxOnMultiValuedField.java?rev=1693625&view=auto
==============================================================================
--- lucene/dev/trunk/solr/core/src/test/org/apache/solr/search/function/TestMinMaxOnMultiValuedField.java (added)
+++ lucene/dev/trunk/solr/core/src/test/org/apache/solr/search/function/TestMinMaxOnMultiValuedField.java Fri Jul 31 16:21:44 2015
@@ -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"
+            );
+  }
+
+}