lucene-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From hoss...@apache.org
Subject [2/2] lucene-solr:branch_7x: SOLR-12962: Added a new 'uninvertible' option for fields and fieldtypes. This defaults to 'true' for backcompat allowing a FieldCache to be built for indexed fields as needed, but users are encouraged to set this to false (us
Date Fri, 09 Nov 2018 16:38:08 GMT
SOLR-12962: Added a new 'uninvertible' option for fields and fieldtypes. This defaults to 'true' for backcompat allowing a FieldCache to be built for indexed fields as needed, but users are encouraged to set this to false (using docValues as needed) to reduce the risk of large fluxuations in heap size due to unexpected attempts to sort/facet/function on non-docValue fields.

(cherry picked from commit 77a4bfaa90637cd3d9a8a2ef4889e163dab143aa)

Conflicts:
	solr/core/src/test/org/apache/solr/BasicFunctionalityTest.java


Project: http://git-wip-us.apache.org/repos/asf/lucene-solr/repo
Commit: http://git-wip-us.apache.org/repos/asf/lucene-solr/commit/36ca27c3
Tree: http://git-wip-us.apache.org/repos/asf/lucene-solr/tree/36ca27c3
Diff: http://git-wip-us.apache.org/repos/asf/lucene-solr/diff/36ca27c3

Branch: refs/heads/branch_7x
Commit: 36ca27c36e90e33abbae8b6ce5359c7655c898c5
Parents: 480eec7
Author: Chris Hostetter <hossman@apache.org>
Authored: Fri Nov 9 08:30:04 2018 -0700
Committer: Chris Hostetter <hossman@apache.org>
Committed: Fri Nov 9 09:37:49 2018 -0700

----------------------------------------------------------------------
 solr/CHANGES.txt                                |   5 +
 .../solr/handler/admin/LukeRequestHandler.java  |   2 +
 .../org/apache/solr/request/SimpleFacets.java   |   7 +
 .../org/apache/solr/schema/CurrencyField.java   |   8 +
 .../org/apache/solr/schema/FieldProperties.java |   4 +-
 .../java/org/apache/solr/schema/FieldType.java  |  11 +-
 .../org/apache/solr/schema/IndexSchema.java     |  16 +-
 .../org/apache/solr/schema/SchemaField.java     |  22 +-
 .../schema/SpatialPointVectorFieldType.java     |   8 +-
 .../solr/search/CollapsingQParserPlugin.java    |  12 +-
 .../apache/solr/search/facet/FacetField.java    |   7 +-
 .../facet/FacetFieldProcessorByArrayUIF.java    |   6 +-
 .../bad-schema-not-indexed-but-uninvertible.xml |  35 +++
 .../solr/collection1/conf/schema-behavior.xml   |  13 +
 .../test-files/solr/collection1/conf/schema.xml |  14 +-
 .../solr/collection1/conf/schema11.xml          |   3 +
 .../solr/collection1/conf/schema_latest.xml     |  12 +
 .../org/apache/solr/BasicFunctionalityTest.java |  32 +-
 .../apache/solr/request/SimpleFacetsTest.java   |  84 +++++
 .../apache/solr/request/TestFacetMethods.java   | 312 ++++++++++---------
 .../solr/rest/schema/TestBulkSchemaAPI.java     |  11 +-
 .../solr/rest/schema/TestFieldResource.java     |   4 +-
 .../solr/rest/schema/TestFieldTypeResource.java |   4 +-
 .../apache/solr/schema/BadIndexSchemaTest.java  |   1 +
 .../SchemaVersionSpecificBehaviorTest.java      |   5 +
 .../solr/search/TestCollapseQParserPlugin.java  |  28 +-
 .../solr/search/facet/TestJsonFacets.java       |  71 +++++
 solr/solr-ref-guide/src/defining-fields.adoc    |   1 +
 .../field-type-definitions-and-properties.adoc  |   1 +
 .../org/apache/solr/common/luke/FieldFlag.java  |   1 +
 .../webapp/web/js/angular/controllers/schema.js |   3 +-
 solr/webapp/web/partials/schema.html            |   7 +
 32 files changed, 557 insertions(+), 193 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/36ca27c3/solr/CHANGES.txt
----------------------------------------------------------------------
diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index ea2108d..bb2dd33 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -107,6 +107,11 @@ New Features
 
 * SOLR-12975: Add ltrim and rtrim Stream Evaluators (Joel Bernstein)
 
+* SOLR-12962: Added a new 'uninvertible' option for fields and fieldtypes. This defaults to 'true' for
+  backcompat allowing a FieldCache to be built for indexed fields as needed, but users are encouraged
+  to set this to false (using docValues as needed) to reduce the risk of large fluxuations in heap
+  size due to unexpected attempts to sort/facet/function on non-docValue fields. (hossman)
+
 Other Changes
 ----------------------
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/36ca27c3/solr/core/src/java/org/apache/solr/handler/admin/LukeRequestHandler.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/LukeRequestHandler.java b/solr/core/src/java/org/apache/solr/handler/admin/LukeRequestHandler.java
index b2d8181..dc147d5 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/LukeRequestHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/LukeRequestHandler.java
@@ -205,6 +205,7 @@ public class LukeRequestHandler extends RequestHandlerBase
     flags.append( (f != null && f.fieldType().tokenized())                   ? FieldFlag.TOKENIZED.getAbbreviation() : '-' );
     flags.append( (f != null && f.fieldType().stored())                      ? FieldFlag.STORED.getAbbreviation() : '-' );
     flags.append( (f != null && f.fieldType().docValuesType() != DocValuesType.NONE)        ? FieldFlag.DOC_VALUES.getAbbreviation() : "-" );
+    flags.append( (false)                                          ? FieldFlag.UNINVERTIBLE.getAbbreviation() : '-' ); // SchemaField Specific
     flags.append( (false)                                          ? FieldFlag.MULTI_VALUED.getAbbreviation() : '-' ); // SchemaField Specific
     flags.append( (f != null && f.fieldType().storeTermVectors())            ? FieldFlag.TERM_VECTOR_STORED.getAbbreviation() : '-' );
     flags.append( (f != null && f.fieldType().storeTermVectorOffsets())   ? FieldFlag.TERM_VECTOR_OFFSET.getAbbreviation() : '-' );
@@ -244,6 +245,7 @@ public class LukeRequestHandler extends RequestHandlerBase
     flags.append( (t != null && t.isTokenized())         ? FieldFlag.TOKENIZED.getAbbreviation() : '-' );
     flags.append( (f != null && f.stored())              ? FieldFlag.STORED.getAbbreviation() : '-' );
     flags.append( (f != null && f.hasDocValues())        ? FieldFlag.DOC_VALUES.getAbbreviation() : "-" );
+    flags.append( (f != null && f.isUninvertible())      ? FieldFlag.UNINVERTIBLE.getAbbreviation() : "-" );
     flags.append( (f != null && f.multiValued())         ? FieldFlag.MULTI_VALUED.getAbbreviation() : '-' );
     flags.append( (f != null && f.storeTermVector() )    ? FieldFlag.TERM_VECTOR_STORED.getAbbreviation() : '-' );
     flags.append( (f != null && f.storeTermOffsets() )   ? FieldFlag.TERM_VECTOR_OFFSET.getAbbreviation() : '-' );

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/36ca27c3/solr/core/src/java/org/apache/solr/request/SimpleFacets.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/request/SimpleFacets.java b/solr/core/src/java/org/apache/solr/request/SimpleFacets.java
index c054757..d78af54 100644
--- a/solr/core/src/java/org/apache/solr/request/SimpleFacets.java
+++ b/solr/core/src/java/org/apache/solr/request/SimpleFacets.java
@@ -670,6 +670,13 @@ public class SimpleFacets {
        method = field.multiValued() ? FacetMethod.FC : FacetMethod.FCS;
      }
 
+     /* Unless isUninvertible() is true, we prohibit any use of UIF...
+        Here we just force FC(S) instead, and trust that the DocValues faceting logic will
+        do the right thing either way (with or w/o docvalues) */
+     if (FacetMethod.UIF == method && ! field.isUninvertible()) {
+       method = field.multiValued() ? FacetMethod.FC : FacetMethod.FCS;
+     }
+     
      /* ENUM can't deal with trie fields that index several terms per value */
      if (method == FacetMethod.ENUM
          && TrieField.getMainValuePrefix(type) != null) {

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/36ca27c3/solr/core/src/java/org/apache/solr/schema/CurrencyField.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/schema/CurrencyField.java b/solr/core/src/java/org/apache/solr/schema/CurrencyField.java
index e2676fe..301dba3 100644
--- a/solr/core/src/java/org/apache/solr/schema/CurrencyField.java
+++ b/solr/core/src/java/org/apache/solr/schema/CurrencyField.java
@@ -67,6 +67,13 @@ public class CurrencyField extends CurrencyFieldType implements SchemaAware, Res
       args.remove(PARAM_PRECISION_STEP);
     }
 
+    // NOTE: because we're not using the PluginLoader to register these field types, they aren't "real"
+    // field types and never get Schema default properties (based on schema.xml's version attribute)
+    // so only the properties explicitly set here (or on the SchemaField's we create from them) are used.
+    //
+    // In theory we should fix this, but since this class is already deprecated, we'll leave it alone
+    // to simplify the risk of back-compat break for existing users.
+    
     // Initialize field type for amount
     fieldTypeAmountRaw = new TrieLongField();
     fieldTypeAmountRaw.setTypeName(FIELD_TYPE_AMOUNT_RAW);
@@ -91,6 +98,7 @@ public class CurrencyField extends CurrencyFieldType implements SchemaAware, Res
     props.put("stored", "false");
     props.put("multiValued", "false");
     props.put("omitNorms", "true");
+    props.put("uninvertible", "true");
     int p = SchemaField.calcProps(name, type, props);
     schema.registerDynamicFields(SchemaField.create(name, type, p, null));
   }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/36ca27c3/solr/core/src/java/org/apache/solr/schema/FieldProperties.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/schema/FieldProperties.java b/solr/core/src/java/org/apache/solr/schema/FieldProperties.java
index 6762345..e0bdfd5 100644
--- a/solr/core/src/java/org/apache/solr/schema/FieldProperties.java
+++ b/solr/core/src/java/org/apache/solr/schema/FieldProperties.java
@@ -54,6 +54,7 @@ public abstract class FieldProperties {
   protected final static int STORE_TERMPAYLOADS  = 0b10000000000000000;
   protected final static int USE_DOCVALUES_AS_STORED  = 0b100000000000000000;
   protected final static int LARGE_FIELD         = 0b1000000000000000000;
+  protected final static int UNINVERTIBLE        = 0b10000000000000000000;
 
   static final String[] propertyNames = {
           "indexed", "tokenized", "stored",
@@ -61,7 +62,8 @@ public abstract class FieldProperties {
           "termVectors", "termPositions", "termOffsets",
           "multiValued",
           "sortMissingFirst","sortMissingLast","required", "omitPositions",
-          "storeOffsetsWithPositions", "docValues", "termPayloads", "useDocValuesAsStored", "large"
+          "storeOffsetsWithPositions", "docValues", "termPayloads", "useDocValuesAsStored", "large",
+          "uninvertible"
   };
 
   static final Map<String,Integer> propertyMap = new HashMap<>();

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/36ca27c3/solr/core/src/java/org/apache/solr/schema/FieldType.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/schema/FieldType.java b/solr/core/src/java/org/apache/solr/schema/FieldType.java
index ae73e09..5919c9e 100644
--- a/solr/core/src/java/org/apache/solr/schema/FieldType.java
+++ b/solr/core/src/java/org/apache/solr/schema/FieldType.java
@@ -168,7 +168,9 @@ public abstract class FieldType extends FieldProperties {
       args.remove("compressThreshold");
     }
     if (schemaVersion >= 1.6f) properties |= USE_DOCVALUES_AS_STORED;
-
+    
+    properties |= UNINVERTIBLE;
+    
     this.args = Collections.unmodifiableMap(args);
     Map<String,String> initArgs = new HashMap<>(args);
     initArgs.remove(CLASS_NAME); // consume the class arg 
@@ -456,12 +458,18 @@ public abstract class FieldType extends FieldProperties {
   }
   
   /**
+   * <p>
    * If DocValues is not enabled for a field, but it's indexed, docvalues can be constructed 
    * on the fly (uninverted, aka fieldcache) on the first request to sort, facet, etc. 
    * This specifies the structure to use.
+   * </p>
+   * <p>
+   * This method will not be used if the field is (effectively) <code>uninvertible="false"</code>
+   * </p>
    * 
    * @param sf field instance
    * @return type to uninvert, or {@code null} (to disallow uninversion for the field)
+   * @see SchemaField#isUninvertible()
    */
   public abstract UninvertingReader.Type getUninversionType(SchemaField sf);
 
@@ -1009,6 +1017,7 @@ public abstract class FieldType extends FieldProperties {
       namedPropertyValues.add(getPropertyName(STORE_OFFSETS), hasProperty(STORE_OFFSETS));
       namedPropertyValues.add(getPropertyName(MULTIVALUED), hasProperty(MULTIVALUED));
       namedPropertyValues.add(getPropertyName(LARGE_FIELD), hasProperty(LARGE_FIELD));
+      namedPropertyValues.add(getPropertyName(UNINVERTIBLE), hasProperty(UNINVERTIBLE));
       if (hasProperty(SORT_MISSING_FIRST)) {
         namedPropertyValues.add(getPropertyName(SORT_MISSING_FIRST), true);
       } else if (hasProperty(SORT_MISSING_LAST)) {

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/36ca27c3/solr/core/src/java/org/apache/solr/schema/IndexSchema.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/schema/IndexSchema.java b/solr/core/src/java/org/apache/solr/schema/IndexSchema.java
index f32a179..3699f4f 100644
--- a/solr/core/src/java/org/apache/solr/schema/IndexSchema.java
+++ b/solr/core/src/java/org/apache/solr/schema/IndexSchema.java
@@ -359,7 +359,21 @@ public class IndexSchema {
       if (sf == null) {
         return null;
       }
-      return sf.getType().getUninversionType(sf);
+
+      if (sf.isUninvertible()) {
+        return sf.getType().getUninversionType(sf);
+      }
+      // else...
+      
+      // It would be nice to throw a helpful error here, with a good useful message for the user,
+      // but unfortunately, inspite of the UninvertingReader class jdoc claims that the uninversion
+      // process is lazy, that doesn't mean it's lazy as of "When a caller attempts ot use doc values"
+      //
+      // The *mapping* function is consulted on LeafReader init/wrap for every FieldInfos found w/o docValues.
+      //
+      // So if we throw an error here instead of returning null, the act of just opening a
+      // newSearcher will trigger that error for any field, even if no one ever attempts to uninvert it
+      return null;
     };
   }
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/36ca27c3/solr/core/src/java/org/apache/solr/schema/SchemaField.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/schema/SchemaField.java b/solr/core/src/java/org/apache/solr/schema/SchemaField.java
index e28629e..7d9449e 100644
--- a/solr/core/src/java/org/apache/solr/schema/SchemaField.java
+++ b/solr/core/src/java/org/apache/solr/schema/SchemaField.java
@@ -88,6 +88,7 @@ public final class SchemaField extends FieldProperties implements IndexableField
   public FieldType getType() { return type; }
   public int getProperties() { return properties; }
 
+  public boolean isUninvertible() { return (properties & UNINVERTIBLE)!=0; }
   public boolean indexed() { return (properties & INDEXED)!=0; }
   public boolean stored() { return (properties & STORED)!=0; }
   public boolean hasDocValues() { return (properties & DOC_VALUES) != 0; }
@@ -171,18 +172,18 @@ public final class SchemaField extends FieldProperties implements IndexableField
                               + getName() + " of type: " + this.type.getTypeName());
     }
     if (! hasDocValues() ) {
-      if ( ! ( indexed() && null != this.type.getUninversionType(this) ) ) {
+      if ( ! ( indexed() && isUninvertible() && null != this.type.getUninversionType(this) ) ) {
         throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, 
-                                "can not sort on a field w/o docValues unless it is indexed and supports Uninversion: " 
+                                "can not sort on a field w/o docValues unless it is indexed=true uninvertible=true and the type supports Uninversion: " 
                                 + getName());
       }
     }
   }
 
   /** 
-   * Sanity checks that the properties of this field type are plausible 
-   * for a field that may be used to get a FieldCacheSource, throwing
-   * an appropriate exception (including the field name) if it is not.  
+   * Sanity checks that the properties of this field type are plausible for a field
+   * that may be used to get a {@link org.apache.lucene.queries.function.valuesource.FieldCacheSource},
+   * throwing an appropriate exception (including the field name) if it is not.  
    * FieldType subclasses can choose to call this method in their 
    * getValueSource implementation 
    * @see FieldType#getValueSource
@@ -194,9 +195,9 @@ public final class SchemaField extends FieldProperties implements IndexableField
                               + getName());
     }
     if (! hasDocValues() ) {
-      if ( ! ( indexed() && null != this.type.getUninversionType(this) ) ) {
+      if ( ! ( indexed() && isUninvertible() && null != this.type.getUninversionType(this) ) ) {
         throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, 
-                                "can not use FieldCache on a field w/o docValues unless it is indexed and supports Uninversion: " 
+                                "can not use FieldCache on a field w/o docValues unless it is indexed uninvertible=true and the type supports Uninversion: " 
                                 + getName());
       }
     }
@@ -247,17 +248,17 @@ public final class SchemaField extends FieldProperties implements IndexableField
 
     if (on(falseProps,INDEXED)) {
       int pp = (INDEXED 
-              | STORE_TERMVECTORS | STORE_TERMPOSITIONS | STORE_TERMOFFSETS | STORE_TERMPAYLOADS);
+              | STORE_TERMVECTORS | STORE_TERMPOSITIONS | STORE_TERMOFFSETS | STORE_TERMPAYLOADS | UNINVERTIBLE);
       if (on(pp,trueProps)) {
         throw new RuntimeException("SchemaField: " + name + " conflicting 'true' field options for non-indexed field:" + props);
       }
       p &= ~pp;
     }
     
-    if (on(falseProps,INDEXED) && on(falseProps,DOC_VALUES)) {
+    if (on(falseProps,UNINVERTIBLE) && on(falseProps,DOC_VALUES)) {
       int pp = (SORT_MISSING_FIRST | SORT_MISSING_LAST);
       if (on(pp,trueProps)) {
-        throw new RuntimeException("SchemaField: " + name + " conflicting 'true' field options for non-indexed/non-docValues field:" + props);
+        throw new RuntimeException("SchemaField: " + name + " conflicting 'true' field options for non-docValues/non-uninvertible field:" + props);
       }
       p &= ~pp;
     }
@@ -341,6 +342,7 @@ public final class SchemaField extends FieldProperties implements IndexableField
       properties.add(getPropertyName(STORE_OFFSETS), storeOffsetsWithPositions());
       properties.add(getPropertyName(MULTIVALUED), multiValued());
       properties.add(getPropertyName(LARGE_FIELD), isLarge());
+      properties.add(getPropertyName(UNINVERTIBLE), isUninvertible());
       if (sortMissingFirst()) {
         properties.add(getPropertyName(SORT_MISSING_FIRST), sortMissingFirst());
       } else if (sortMissingLast()) {

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/36ca27c3/solr/core/src/java/org/apache/solr/schema/SpatialPointVectorFieldType.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/schema/SpatialPointVectorFieldType.java b/solr/core/src/java/org/apache/solr/schema/SpatialPointVectorFieldType.java
index 64e42ef..006d8d2 100644
--- a/solr/core/src/java/org/apache/solr/schema/SpatialPointVectorFieldType.java
+++ b/solr/core/src/java/org/apache/solr/schema/SpatialPointVectorFieldType.java
@@ -64,8 +64,12 @@ public class SpatialPointVectorFieldType extends AbstractSpatialFieldType<PointV
     }
     precisionStep = ((TrieField)fieldType).getPrecisionStep();
 
-    //Just set these, delegate everything else to the field type
-    final int p = (INDEXED | TOKENIZED | OMIT_NORMS | OMIT_TF_POSITIONS);
+    // NOTE: the SchemaField constructor we're using ignores any properties of the fieldType
+    // so only the ones we're explicitly setting get used.
+    //
+    // In theory we should fix this, but since this class is already deprecated, we'll leave it alone
+    // to simplify the risk of back-compat break for existing users.
+    final int p = (INDEXED | TOKENIZED | OMIT_NORMS | OMIT_TF_POSITIONS | UNINVERTIBLE);
     List<SchemaField> newFields = new ArrayList<>();
     for( SchemaField sf : schema.getFields().values() ) {
       if( sf.getType() == this ) {

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/36ca27c3/solr/core/src/java/org/apache/solr/search/CollapsingQParserPlugin.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/search/CollapsingQParserPlugin.java b/solr/core/src/java/org/apache/solr/search/CollapsingQParserPlugin.java
index 0988c48..47771eb 100644
--- a/solr/core/src/java/org/apache/solr/search/CollapsingQParserPlugin.java
+++ b/solr/core/src/java/org/apache/solr/search/CollapsingQParserPlugin.java
@@ -391,9 +391,19 @@ public class CollapsingQParserPlugin extends QParserPlugin {
    * This is VERY fast at query time but slower to warm and causes insanity.
    */
   public static LeafReader getTopFieldCacheReader(SolrIndexSearcher searcher, String collapseField) {
+    UninvertingReader.Type type = null;
+    final SchemaField f = searcher.getSchema().getFieldOrNull(collapseField);
+    assert null != f;        // should already be enforced higher up
+    assert !f.multiValued(); // should already be enforced higher up
+    
+    assert f.getType() instanceof StrField; // this method shouldn't be called otherwise
+    if (f.indexed() && f.isUninvertible()) {
+      type = UninvertingReader.Type.SORTED;
+    }
+    
     return UninvertingReader.wrap(
         new ReaderWrapper(searcher.getSlowAtomicReader(), collapseField),
-        Collections.singletonMap(collapseField, UninvertingReader.Type.SORTED)::get);
+        Collections.singletonMap(collapseField, type)::get);
   }
 
   private static class ReaderWrapper extends FilterLeafReader {

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/36ca27c3/solr/core/src/java/org/apache/solr/search/facet/FacetField.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/search/facet/FacetField.java b/solr/core/src/java/org/apache/solr/search/facet/FacetField.java
index 6388e74..a5ca1df 100644
--- a/solr/core/src/java/org/apache/solr/search/facet/FacetField.java
+++ b/solr/core/src/java/org/apache/solr/search/facet/FacetField.java
@@ -24,6 +24,7 @@ import org.apache.solr.schema.FieldType;
 import org.apache.solr.schema.NumberType;
 import org.apache.solr.schema.SchemaField;
 
+
 // Any type of facet request that generates a variable number of buckets
 // and the ability to sort by those generated buckets.
 abstract class FacetRequestSorted extends FacetRequest {
@@ -110,7 +111,7 @@ public class FacetField extends FacetRequestSorted {
 
     if (fcontext.facetInfo != null) {
       // refinement... we will end up either skipping the entire facet, or doing calculating only specific facet buckets
-      if (multiToken && !sf.hasDocValues() && method!=FacetMethod.DV) {
+      if (multiToken && !sf.hasDocValues() && method!=FacetMethod.DV && sf.isUninvertible()) {
         // Match the access method from the first phase.
         // It won't always matter, but does currently for an all-values bucket
         return new FacetFieldProcessorByArrayUIF(fcontext, this, sf);
@@ -118,7 +119,7 @@ public class FacetField extends FacetRequestSorted {
       return new FacetFieldProcessorByArrayDV(fcontext, this, sf);
     }
 
-      NumberType ntype = ft.getNumberType();
+    NumberType ntype = ft.getNumberType();
     // ensure we can support the requested options for numeric faceting:
     if (ntype != null) {
       if (prefix != null) {
@@ -163,7 +164,7 @@ public class FacetField extends FacetRequestSorted {
 
     // multi-valued after this point
 
-    if (sf.hasDocValues() || method == FacetMethod.DV) {
+    if (sf.hasDocValues() || method == FacetMethod.DV || !sf.isUninvertible()) {
       // single and multi-valued string docValues
       return new FacetFieldProcessorByArrayDV(fcontext, this, sf);
     }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/36ca27c3/solr/core/src/java/org/apache/solr/search/facet/FacetFieldProcessorByArrayUIF.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/search/facet/FacetFieldProcessorByArrayUIF.java b/solr/core/src/java/org/apache/solr/search/facet/FacetFieldProcessorByArrayUIF.java
index d5cceef..6c90b3e 100644
--- a/solr/core/src/java/org/apache/solr/search/facet/FacetFieldProcessorByArrayUIF.java
+++ b/solr/core/src/java/org/apache/solr/search/facet/FacetFieldProcessorByArrayUIF.java
@@ -14,7 +14,6 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 package org.apache.solr.search.facet;
 
 import java.io.IOException;
@@ -22,6 +21,7 @@ import java.io.IOException;
 import org.apache.lucene.index.TermsEnum;
 import org.apache.lucene.util.BytesRef;
 import org.apache.lucene.util.UnicodeUtil;
+import org.apache.solr.common.SolrException;
 import org.apache.solr.schema.SchemaField;
 
 /** {@link UnInvertedField} implementation of field faceting.
@@ -32,6 +32,10 @@ class FacetFieldProcessorByArrayUIF extends FacetFieldProcessorByArray {
 
   FacetFieldProcessorByArrayUIF(FacetContext fcontext, FacetField freq, SchemaField sf) {
     super(fcontext, freq, sf);
+    if (! sf.isUninvertible()) {
+      throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
+                              getClass()+" can not be used on fields where uninvertible='false'");
+    }
   }
 
   @Override

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/36ca27c3/solr/core/src/test-files/solr/collection1/conf/bad-schema-not-indexed-but-uninvertible.xml
----------------------------------------------------------------------
diff --git a/solr/core/src/test-files/solr/collection1/conf/bad-schema-not-indexed-but-uninvertible.xml b/solr/core/src/test-files/solr/collection1/conf/bad-schema-not-indexed-but-uninvertible.xml
new file mode 100644
index 0000000..8f814df
--- /dev/null
+++ b/solr/core/src/test-files/solr/collection1/conf/bad-schema-not-indexed-but-uninvertible.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" ?>
+<!--
+ 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.
+-->
+
+<schema name="bad-schema-not-indexed-but-uninvertible" version="1.6">
+  <fieldType name="string" class="solr.StrField"/>
+
+  <field name="id" type="string" indexed="true" stored="true" multiValued="false" required="false"/>
+
+  <field name="signatureField" type="string" indexed="true" stored="false"/>
+
+  <!-- BEGIN BAD STUFF -->
+  <field name="bad_field" type="string" indexed="false" uninvertible="true" />
+  <!-- END BAD STUFF -->
+
+  <dynamicField name="*_sS" type="string" indexed="false" stored="true"/>
+
+
+  <uniqueKey>id</uniqueKey>
+
+</schema>

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/36ca27c3/solr/core/src/test-files/solr/collection1/conf/schema-behavior.xml
----------------------------------------------------------------------
diff --git a/solr/core/src/test-files/solr/collection1/conf/schema-behavior.xml b/solr/core/src/test-files/solr/collection1/conf/schema-behavior.xml
index 2fb50c7..663de18 100644
--- a/solr/core/src/test-files/solr/collection1/conf/schema-behavior.xml
+++ b/solr/core/src/test-files/solr/collection1/conf/schema-behavior.xml
@@ -50,6 +50,9 @@
   <fieldType name="int_dvas_t" class="${solr.tests.IntegerFieldType}" docValues="${solr.tests.numeric.dv}" useDocValuesAsStored="true"/>
   <fieldType name="int_dvas_f" class="${solr.tests.IntegerFieldType}" docValues="${solr.tests.numeric.dv}" useDocValuesAsStored="false"/>
 
+  <fieldType name="str_uninvert_f" class="solr.StrField" uninvertible="false"/>
+  <fieldType name="str_uninvert_t" class="solr.StrField" uninvertible="true"/>
+  
   <!-- all behavior is default -->
 
   <field name="text" type="text"/>
@@ -98,6 +101,11 @@
   <dynamicField name="*_dyn_ft_intdvas_t" type="int_dvas_t"/>
   <dynamicField name="*_dyn_ft_intdvas_f" type="int_dvas_f"/>
 
+  <field name="ft_uninvert_t" type="str_uninvert_t"/>
+  <field name="ft_uninvert_f" type="str_uninvert_f"/>
+  <dynamicField name="*_dyn_ft_uninvert_t" type="str_uninvert_t"/>
+  <dynamicField name="*_dyn_ft_uninvert_f" type="str_uninvert_f"/>
+
   <!-- explicit props on field -->
   <field name="multi_f" type="str" multiValued="false"/>
   <field name="multi_t" type="str" multiValued="true"/>
@@ -128,5 +136,10 @@
   <field name="intdvas_f" type="int" useDocValuesAsStored="false"/>
   <dynamicField name="*_dyn_intdvas_t" type="int" useDocValuesAsStored="true"/>
   <dynamicField name="*_dyn_intdvas_f" type="int" useDocValuesAsStored="false"/>
+  
+  <field name="uninvert_t" type="str" uninvertible="true"/>
+  <field name="uninvert_f" type="str" uninvertible="false"/>
+  <dynamicField name="*_dyn_uninvert_t" type="str" uninvertible="true"/>
+  <dynamicField name="*_dyn_uninvert_f" type="str" uninvertible="false"/>
 
 </schema>

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/36ca27c3/solr/core/src/test-files/solr/collection1/conf/schema.xml
----------------------------------------------------------------------
diff --git a/solr/core/src/test-files/solr/collection1/conf/schema.xml b/solr/core/src/test-files/solr/collection1/conf/schema.xml
index b1a261b..f4410f8 100644
--- a/solr/core/src/test-files/solr/collection1/conf/schema.xml
+++ b/solr/core/src/test-files/solr/collection1/conf/schema.xml
@@ -641,6 +641,8 @@
 
   <field name="payloadDelimited" type="payloadDelimited"/>
 
+  <field name="sortabuse_not_uninvertible" type="string" indexed="true" multiValued="false" uninvertible="false" />
+  
   <!-- EnumType -->
   <field name="severity" type="severityType" docValues="true" indexed="true" stored="true" multiValued="false"/>
 
@@ -756,7 +758,13 @@
   <dynamicField name="*_dd_dvo" multiValued="true" type="double" docValues="true" indexed="false" stored="false"
                 useDocValuesAsStored="true"/>
                 
-                
+  <!-- Indexed, but NOT uninvertible -->
+  <dynamicField name="*_s_not_uninvert" type="string" indexed="true" stored="false" docValues="false" uninvertible="false" />
+  <!-- docValues, but NOT uninvertible -->
+  <dynamicField name="*_s_not_uninvert_dv" type="string" indexed="true" stored="false" docValues="true" uninvertible="false" />
+
+
+  
   <!-- Only Stored numerics -->
   <dynamicField name="*_i_os" type="int" indexed="false" stored="true" docValues="false"/>
   <dynamicField name="*_l_os" type="long" indexed="false" stored="true" docValues="false"/>
@@ -826,6 +834,10 @@
   <copyField source="range_facet_f" dest="range_facet_d_dv"/>
   <copyField source="bday" dest="range_facet_dt_dv"/>
 
+  <copyField source="trait_s" dest="trait_s_not_uninvert"/>
+  <copyField source="trait_s" dest="trait_s_not_uninvert_dv"/>
+  <copyField source="trait_s" dest="trait_s_not_indexed_sS"/>
+  
   <!-- dynamic destination -->
   <copyField source="*_dynamic" dest="dynamic_*"/>
   <copyField source="*_path" dest="*_ancestor"/>

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/36ca27c3/solr/core/src/test-files/solr/collection1/conf/schema11.xml
----------------------------------------------------------------------
diff --git a/solr/core/src/test-files/solr/collection1/conf/schema11.xml b/solr/core/src/test-files/solr/collection1/conf/schema11.xml
index d3344a2..0dff57f 100644
--- a/solr/core/src/test-files/solr/collection1/conf/schema11.xml
+++ b/solr/core/src/test-files/solr/collection1/conf/schema11.xml
@@ -455,6 +455,9 @@ valued. -->
    <dynamicField name="*_dt" type="date"    indexed="true"  stored="true"/>
    <dynamicField name="*_ws" type="text_ws" indexed="true"  stored="true"/>
    
+  <!-- Indexed, but NOT uninvertible -->
+  <dynamicField name="*_s_not_uninvert" type="string" indexed="true" stored="false" docValues="false" uninvertible="false" />
+   
    <!-- for testing tfidf functions, see TestFunctionQuery.testTFIDFFunctions -->
    <dynamicField name="*_tfidf"  type="tfidf_text"    indexed="true"  stored="true" />
    <fieldType name="tfidf_text" class="solr.TextField" positionIncrementGap="100">

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/36ca27c3/solr/core/src/test-files/solr/collection1/conf/schema_latest.xml
----------------------------------------------------------------------
diff --git a/solr/core/src/test-files/solr/collection1/conf/schema_latest.xml b/solr/core/src/test-files/solr/collection1/conf/schema_latest.xml
index 70abb39..78d5766 100644
--- a/solr/core/src/test-files/solr/collection1/conf/schema_latest.xml
+++ b/solr/core/src/test-files/solr/collection1/conf/schema_latest.xml
@@ -292,6 +292,12 @@
   <field name="signatureField" type="string" indexed="true" stored="false"/>
   <dynamicField name="*_sS" type="string" indexed="false" stored="true"/>
 
+  <!-- Indexed, but NOT uninvertible -->
+  <field name="where_s_multi_not_uninvert" type="string" indexed="true" stored="false" docValues="false" uninvertible="false" multiValued="true" />
+  <field name="where_s_single_not_uninvert" type="string" indexed="true" stored="false" docValues="false" uninvertible="false" multiValued="false" />
+  <!-- docValues, but NOT uninvertible -->
+  <field name="where_s_multi_not_uninvert_dv" type="string" indexed="true" stored="false" docValues="true" uninvertible="false" multiValued="true" />
+  <field name="where_s_single_not_uninvert_dv" type="string" indexed="true" stored="false" docValues="true" uninvertible="false" multiValued="false" />
 
   <!-- Field to use to determine and enforce document uniqueness. 
        Unless this field is marked with required="false", it will be a required field
@@ -324,6 +330,12 @@
 
   <!-- Create a string version of author for faceting -->
   <copyField source="author" dest="author_s"/>
+  
+  <copyField source="where_s" dest="where_s_multi_not_uninvert"/>
+  <copyField source="where_s" dest="where_s_multi_not_uninvert_dv"/>
+  <copyField source="where_s" dest="where_s_single_not_uninvert"/>
+  <copyField source="where_s" dest="where_s_single_not_uninvert_dv"/>
+  <copyField source="where_s" dest="where_s_not_indexed_sS"/>
 
   <!-- Above, multiple source fields are copied to the [text] field. 
    Another way to map multiple source fields to the same 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/36ca27c3/solr/core/src/test/org/apache/solr/BasicFunctionalityTest.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/BasicFunctionalityTest.java b/solr/core/src/test/org/apache/solr/BasicFunctionalityTest.java
index c26be9d..b891e18 100644
--- a/solr/core/src/test/org/apache/solr/BasicFunctionalityTest.java
+++ b/solr/core/src/test/org/apache/solr/BasicFunctionalityTest.java
@@ -22,6 +22,7 @@ import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.StringWriter;
 import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.LinkedList;
 import java.util.List;
@@ -994,32 +995,31 @@ public class BasicFunctionalityTest extends SolrTestCaseJ4 {
   public void testAbuseOfSort() {
 
     assertU(adoc("id", "9999991",
-                 "sortabuse_b", "true",
+                 "sortabuse_not_uninvertible", "xxx",
                  "sortabuse_t", "zzz xxx ccc vvv bbb nnn aaa sss ddd fff ggg"));
     assertU(adoc("id", "9999992",
-                 "sortabuse_b", "true",
+                 "sortabuse_not_uninvertible", "yyy",
                  "sortabuse_t", "zzz xxx ccc vvv bbb nnn qqq www eee rrr ttt"));
 
     assertU(commit());
-  
-    try {
-      ignoreException("can not sort on multivalued field: sortabuse_t");
-      assertQ("sort on something that shouldn't work",
-              req("q", "sortabuse_b:true",
-                  "sort", "sortabuse_t asc"),
-              "*[count(//doc)=2]");
-      fail("no error encountered when sorting on sortabuse_t");
-    } catch (Exception outer) {
-      // EXPECTED
-      Throwable root = getRootCause(outer);
-      assertEquals("sort exception root cause", 
+
+    for (String f : Arrays.asList("sortabuse_not_uninvertible", "sortabuse_t")) {
+      RuntimeException outerEx = expectThrows(RuntimeException.class, () -> {
+          ignoreException("sortabuse");
+          assertQ("sort on something that shouldn't work",
+                  req("q", "*:*",
+                      "sort", f+ " asc"),
+                  "*[count(//doc)=2]");
+        });
+      Throwable root = getRootCause(outerEx);
+      assertEquals("sort exception root cause",
                    SolrException.class, root.getClass());
       SolrException e = (SolrException) root;
-      assertEquals("incorrect error type", 
+      assertEquals("incorrect error type",
                    SolrException.ErrorCode.BAD_REQUEST,
                    SolrException.ErrorCode.getErrorCode(e.code()));
       assertTrue("exception doesn't contain field name",
-                 -1 != e.getMessage().indexOf("sortabuse_t"));
+                 e.getMessage().contains(f));
     }
   }
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/36ca27c3/solr/core/src/test/org/apache/solr/request/SimpleFacetsTest.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/request/SimpleFacetsTest.java b/solr/core/src/test/org/apache/solr/request/SimpleFacetsTest.java
index e3100e2..6667d89 100644
--- a/solr/core/src/test/org/apache/solr/request/SimpleFacetsTest.java
+++ b/solr/core/src/test/org/apache/solr/request/SimpleFacetsTest.java
@@ -33,6 +33,7 @@ import org.apache.solr.common.params.FacetParams.FacetRangeInclude;
 import org.apache.solr.common.params.FacetParams.FacetRangeMethod;
 import org.apache.solr.common.params.FacetParams.FacetRangeOther;
 import org.apache.solr.common.params.ModifiableSolrParams;
+import org.apache.solr.common.params.SolrParams;
 import org.apache.solr.common.util.NamedList;
 import org.apache.solr.response.SolrQueryResponse;
 import org.apache.solr.schema.NumberType;
@@ -842,6 +843,89 @@ public class SimpleFacetsTest extends SolrTestCaseJ4 {
     );
   }
 
+  public void testBehaviorEquivilenceOfUninvertibleFalse() throws Exception {
+    // NOTE: mincount=0 affects method detection/coercion, so we include permutations of it
+    
+    { 
+      // an "uninvertible=false" field is not be facetable using the "default" method,
+      // or any explicit method other then "enum".
+      //
+      // it should behave the same as any attempt (using any method) at faceting on
+      // and "indexed=false docValues=false" field -- returning no buckets.
+      
+      final List<SolrParams> paramSets = new ArrayList<>();
+      for (String min : Arrays.asList("0", "1")) {
+        for (String f : Arrays.asList("trait_s_not_uninvert", "trait_s_not_indexed_sS")) {
+          paramSets.add(params("facet.field", "{!key=x}" + f));
+          for (String method : Arrays.asList("fc", "fcs", "uif")) {
+            paramSets.add(params("facet.field", "{!key=x}" + f,
+                                 "facet.mincount", min,
+                                 "facet.method", method));
+            paramSets.add(params("facet.field", "{!key=x}" + f,
+                                 "facet.mincount", min,
+                                 "facet.method", method));
+          }
+        }
+        paramSets.add(params("facet.field", "{!key=x}trait_s_not_indexed_sS",
+                             "facet.mincount", min,
+                             "facet.method", "enum"));
+      }
+      for (SolrParams p : paramSets) {
+        // "empty" results should be the same regardless of mincount
+        assertQ("expect no buckets when field is not-indexed or not-uninvertible",
+                req(p
+                    ,"rows","0"
+                    ,"q", "id_i1:[42 TO 47]"
+                    ,"fq", "id_i1:[42 TO 45]"
+                    ,"facet", "true"
+                    )
+                ,"//*[@numFound='4']"
+                ,"*[count(//lst[@name='x'])=1]"
+                ,"*[count(//lst[@name='x']/int)=0]"
+                );
+      }
+      
+    }
+    
+    { 
+      // the only way to facet on an "uninvertible=false" field is to explicitly request facet.method=enum
+      // in which case it should behave consistently with it's copyField source & equivilent docValues field
+      // (using any method for either of them)
+
+      final List<SolrParams> paramSets = new ArrayList<>();
+      for (String min : Arrays.asList("0", "1")) {
+        paramSets.add(params("facet.field", "{!key=x}trait_s_not_uninvert",
+                             "facet.method", "enum"));
+        for (String okField : Arrays.asList("trait_s", "trait_s_not_uninvert_dv")) {
+          paramSets.add(params("facet.field", "{!key=x}" + okField));
+          for (String method : Arrays.asList("enum","fc", "fcs", "uif")) {
+            paramSets.add(params("facet.field", "{!key=x}" + okField,
+                                 "facet.method", method));
+          }
+        }
+        for (SolrParams p : paramSets) {
+          assertQ("check counts for applied facet queries using filtering (fq)",
+                  req(p
+                      ,"rows","0"
+                      ,"q", "id_i1:[42 TO 47]"
+                      ,"fq", "id_i1:[42 TO 45]"
+                      ,"facet", "true"
+                      ,"facet.mincount", min
+                      )
+                  ,"//*[@numFound='4']"
+                  ,"*[count(//lst[@name='x'])=1]"
+                  ,"*[count(//lst[@name='x']/int)="+("0".equals(min) ? "4]" : "3]")
+                  ,"//lst[@name='x']/int[@name='Tool'][.='2']"
+                  ,"//lst[@name='x']/int[@name='Obnoxious'][.='1']"
+                  ,"//lst[@name='x']/int[@name='Chauvinist'][.='1']"
+                  ,"count(//lst[@name='x']/int[@name='Pig'][.='0'])=" + ("0".equals(min) ? "1" : "0")
+                  );
+        }
+      }
+    }
+  }
+
+  
   public static void indexDateFacets() {
     final String i = "id";
     final String f = "bday";

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/36ca27c3/solr/core/src/test/org/apache/solr/request/TestFacetMethods.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/request/TestFacetMethods.java b/solr/core/src/test/org/apache/solr/request/TestFacetMethods.java
index fa23494..e70083d 100644
--- a/solr/core/src/test/org/apache/solr/request/TestFacetMethods.java
+++ b/solr/core/src/test/org/apache/solr/request/TestFacetMethods.java
@@ -17,8 +17,7 @@
 
 package org.apache.solr.request;
 
-import static junit.framework.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
+import java.util.Arrays;
 
 import org.apache.solr.request.SimpleFacets.FacetMethod;
 import org.apache.solr.schema.BoolField;
@@ -26,201 +25,214 @@ import org.apache.solr.schema.IntPointField;
 import org.apache.solr.schema.SchemaField;
 import org.apache.solr.schema.StrField;
 import org.apache.solr.schema.TrieIntField;
+import org.apache.lucene.util.LuceneTestCase;
 import org.junit.Test;
 
-public class TestFacetMethods {
+public class TestFacetMethods extends LuceneTestCase {
 
   // TODO - make these public in FieldProperties?
   protected final static int MULTIVALUED         = 0x00000200;
   protected final static int DOC_VALUES          = 0x00008000;
+  protected final static int UNINVERTIBLE        = 0b10000000000000000000;
+
+  protected static boolean propsMatch( int x, int y ) {
+    return (x & y) != 0;
+  }
 
   @Test
   public void testNumericSingleValuedDV() {
 
-    SchemaField field = new SchemaField("field", new TrieIntField(), DOC_VALUES, null);
-
-    // default is FCS, can't use ENUM due to trie-field terms, FC rewrites to FCS for efficiency
-
-    assertEquals(SimpleFacets.FacetMethod.FCS, SimpleFacets.selectFacetMethod(field, null, 0));
-    assertEquals(SimpleFacets.FacetMethod.FCS, SimpleFacets.selectFacetMethod(field, SimpleFacets.FacetMethod.ENUM, 0));
-    assertEquals(SimpleFacets.FacetMethod.FCS, SimpleFacets.selectFacetMethod(field, SimpleFacets.FacetMethod.FC, 0));
-    assertEquals(SimpleFacets.FacetMethod.UIF, SimpleFacets.selectFacetMethod(field, SimpleFacets.FacetMethod.UIF, 0));
-    assertEquals(SimpleFacets.FacetMethod.FCS, SimpleFacets.selectFacetMethod(field, SimpleFacets.FacetMethod.FCS, 0));
-    assertEquals(SimpleFacets.FacetMethod.FCS, SimpleFacets.selectFacetMethod(field, null, 1));
-    assertEquals(SimpleFacets.FacetMethod.FCS, SimpleFacets.selectFacetMethod(field, SimpleFacets.FacetMethod.ENUM, 1));
-    assertEquals(SimpleFacets.FacetMethod.FCS, SimpleFacets.selectFacetMethod(field, SimpleFacets.FacetMethod.FC, 1));
-    assertEquals(SimpleFacets.FacetMethod.UIF, SimpleFacets.selectFacetMethod(field, SimpleFacets.FacetMethod.UIF, 1));
-    assertEquals(SimpleFacets.FacetMethod.FCS, SimpleFacets.selectFacetMethod(field, SimpleFacets.FacetMethod.FCS, 1));
-
+    for (int props : Arrays.asList(DOC_VALUES ^ UNINVERTIBLE,
+                                   DOC_VALUES)) {
+      SchemaField field = new SchemaField("field", new TrieIntField(), props, null);
+      // default is FCS, can't use ENUM due to trie-field terms, FC rewrites to FCS for efficiency
+      for (int mincount : Arrays.asList(0, 1)) {
+        // behavior should be independent of mincount
+        assertEquals(FacetMethod.FCS, SimpleFacets.selectFacetMethod(field, null, mincount));
+        assertEquals(FacetMethod.FCS, SimpleFacets.selectFacetMethod(field, FacetMethod.ENUM, mincount));
+        assertEquals(FacetMethod.FCS, SimpleFacets.selectFacetMethod(field, FacetMethod.FC, mincount));
+        assertEquals(FacetMethod.FCS, SimpleFacets.selectFacetMethod(field, FacetMethod.FCS, mincount));
+        
+        // UIF only allowed if field is UNINVERTIBLE
+        assertEquals(propsMatch(props, UNINVERTIBLE) ? FacetMethod.UIF : FacetMethod.FCS,
+                     SimpleFacets.selectFacetMethod(field, FacetMethod.UIF, 0));
+      }
+    }
   }
 
   @Test
   public void testNumericMultiValuedDV() {
 
-    SchemaField field = new SchemaField("field", new TrieIntField(), DOC_VALUES ^ MULTIVALUED, null);
-
-    // default is FC, can't use ENUM due to trie-field terms, can't use FCS because of multivalues
-
-    // default value is FC
-    assertEquals(SimpleFacets.FacetMethod.FC, SimpleFacets.selectFacetMethod(field, null, 0));
-    assertEquals(SimpleFacets.FacetMethod.FC, SimpleFacets.selectFacetMethod(field, SimpleFacets.FacetMethod.ENUM, 0));
-    assertEquals(SimpleFacets.FacetMethod.FC, SimpleFacets.selectFacetMethod(field, SimpleFacets.FacetMethod.FCS, 0));
-    assertEquals(SimpleFacets.FacetMethod.UIF, SimpleFacets.selectFacetMethod(field, SimpleFacets.FacetMethod.UIF, 0));
-    assertEquals(SimpleFacets.FacetMethod.FC, SimpleFacets.selectFacetMethod(field, SimpleFacets.FacetMethod.FC, 0));
-    assertEquals(SimpleFacets.FacetMethod.FC, SimpleFacets.selectFacetMethod(field, null, 1));
-    assertEquals(SimpleFacets.FacetMethod.FC, SimpleFacets.selectFacetMethod(field, SimpleFacets.FacetMethod.ENUM, 1));
-    assertEquals(SimpleFacets.FacetMethod.FC, SimpleFacets.selectFacetMethod(field, SimpleFacets.FacetMethod.FCS, 1));
-    assertEquals(SimpleFacets.FacetMethod.UIF, SimpleFacets.selectFacetMethod(field, SimpleFacets.FacetMethod.UIF, 1));
-    assertEquals(SimpleFacets.FacetMethod.FC, SimpleFacets.selectFacetMethod(field, SimpleFacets.FacetMethod.FC, 1));
-
+    for (int props : Arrays.asList(DOC_VALUES ^ MULTIVALUED ^ UNINVERTIBLE,
+                                   DOC_VALUES ^ MULTIVALUED)) {
+      SchemaField field = new SchemaField("field", new TrieIntField(), props, null);
+      // default value is FC
+      for (int mincount : Arrays.asList(0, 1)) {
+        // behavior should be independent of mincount
+        assertEquals(FacetMethod.FC, SimpleFacets.selectFacetMethod(field, null, mincount));
+        assertEquals(FacetMethod.FC, SimpleFacets.selectFacetMethod(field, FacetMethod.ENUM, mincount));
+        assertEquals(FacetMethod.FC, SimpleFacets.selectFacetMethod(field, FacetMethod.FCS, mincount));
+        assertEquals(FacetMethod.FC, SimpleFacets.selectFacetMethod(field, FacetMethod.FC, mincount));
+        
+        // UIF only allowed if field is UNINVERTIBLE
+        assertEquals(propsMatch(props, UNINVERTIBLE) ? FacetMethod.UIF : FacetMethod.FC,
+                     SimpleFacets.selectFacetMethod(field, FacetMethod.UIF, mincount));
+      }
+    }
   }
 
   @Test
   public void testNumericSingleValuedNoDV() {
 
-    SchemaField field = new SchemaField("field", new TrieIntField(), 0, null);
-
-    // only works with FCS for mincount = 0, UIF for count > 0 is fine
-
-    assertEquals(SimpleFacets.FacetMethod.FCS, SimpleFacets.selectFacetMethod(field, null, 0));
-    assertEquals(SimpleFacets.FacetMethod.FCS, SimpleFacets.selectFacetMethod(field, SimpleFacets.FacetMethod.ENUM, 0));
-    assertEquals(SimpleFacets.FacetMethod.FCS, SimpleFacets.selectFacetMethod(field, SimpleFacets.FacetMethod.FC, 0));
-    assertEquals(SimpleFacets.FacetMethod.FCS, SimpleFacets.selectFacetMethod(field, SimpleFacets.FacetMethod.UIF, 0));
-    assertEquals(SimpleFacets.FacetMethod.FCS, SimpleFacets.selectFacetMethod(field, SimpleFacets.FacetMethod.FCS, 0));
-    assertEquals(SimpleFacets.FacetMethod.FCS, SimpleFacets.selectFacetMethod(field, null, 1));
-    assertEquals(SimpleFacets.FacetMethod.FCS, SimpleFacets.selectFacetMethod(field, SimpleFacets.FacetMethod.ENUM, 1));
-    assertEquals(SimpleFacets.FacetMethod.FCS, SimpleFacets.selectFacetMethod(field, SimpleFacets.FacetMethod.FC, 1));
-    assertEquals(SimpleFacets.FacetMethod.UIF, SimpleFacets.selectFacetMethod(field, SimpleFacets.FacetMethod.UIF, 1));
-    assertEquals(SimpleFacets.FacetMethod.FCS, SimpleFacets.selectFacetMethod(field, SimpleFacets.FacetMethod.FCS, 1));
-
+    for (int props : Arrays.asList(0 ^ UNINVERTIBLE,
+                                   0)) {
+      SchemaField field = new SchemaField("field", new TrieIntField(), props, null);
+      // FCS is used by default for most requested methods other then UIF -- regardless of mincount
+      for (int mincount : Arrays.asList(0, 1)) {
+        assertEquals(FacetMethod.FCS, SimpleFacets.selectFacetMethod(field, null, mincount));
+        assertEquals(FacetMethod.FCS, SimpleFacets.selectFacetMethod(field, FacetMethod.ENUM, mincount));
+        assertEquals(FacetMethod.FCS, SimpleFacets.selectFacetMethod(field, FacetMethod.FC, mincount));
+        assertEquals(FacetMethod.FCS, SimpleFacets.selectFacetMethod(field, FacetMethod.FCS, mincount));
+      }
+      // UIF allowed only if UNINVERTIBLE *AND* mincount > 0
+      assertEquals(FacetMethod.FCS, SimpleFacets.selectFacetMethod(field, FacetMethod.UIF, 0));
+      assertEquals(propsMatch(props, UNINVERTIBLE) ? FacetMethod.UIF : FacetMethod.FCS,
+                   SimpleFacets.selectFacetMethod(field, FacetMethod.UIF, 1));
+    }
   }
 
   @Test
   public void testNumericMultiValuedNoDV() {
 
-    SchemaField field = new SchemaField("field", new TrieIntField(), MULTIVALUED, null);
-
-    // only works with FC for mincount = 0, UIF for count > 1 is fine
-
-    assertEquals(SimpleFacets.FacetMethod.FC, SimpleFacets.selectFacetMethod(field, null, 0));
-    assertEquals(SimpleFacets.FacetMethod.FC, SimpleFacets.selectFacetMethod(field, SimpleFacets.FacetMethod.ENUM, 0));
-    assertEquals(SimpleFacets.FacetMethod.FC, SimpleFacets.selectFacetMethod(field, SimpleFacets.FacetMethod.FCS, 0));
-    assertEquals(SimpleFacets.FacetMethod.FC, SimpleFacets.selectFacetMethod(field, SimpleFacets.FacetMethod.UIF, 0));
-    assertEquals(SimpleFacets.FacetMethod.FC, SimpleFacets.selectFacetMethod(field, SimpleFacets.FacetMethod.FC, 0));
-    assertEquals(SimpleFacets.FacetMethod.FC, SimpleFacets.selectFacetMethod(field, null, 1));
-    assertEquals(SimpleFacets.FacetMethod.FC, SimpleFacets.selectFacetMethod(field, SimpleFacets.FacetMethod.ENUM, 1));
-    assertEquals(SimpleFacets.FacetMethod.FC, SimpleFacets.selectFacetMethod(field, SimpleFacets.FacetMethod.FCS, 1));
-    assertEquals(SimpleFacets.FacetMethod.UIF, SimpleFacets.selectFacetMethod(field, SimpleFacets.FacetMethod.UIF, 1));
-    assertEquals(SimpleFacets.FacetMethod.FC, SimpleFacets.selectFacetMethod(field, SimpleFacets.FacetMethod.FC, 1));
-
+    for (int props : Arrays.asList(MULTIVALUED ^ UNINVERTIBLE,
+                                   MULTIVALUED)) {
+      SchemaField field = new SchemaField("field", new TrieIntField(), props, null);
+      // FC is used by default for most requested methods other then UIF -- regardless of mincount
+      for (int mincount : Arrays.asList(0, 1)) {
+        assertEquals(FacetMethod.FC, SimpleFacets.selectFacetMethod(field, null, mincount));
+        assertEquals(FacetMethod.FC, SimpleFacets.selectFacetMethod(field, FacetMethod.ENUM, mincount));
+        assertEquals(FacetMethod.FC, SimpleFacets.selectFacetMethod(field, FacetMethod.FC, mincount));
+        assertEquals(FacetMethod.FC, SimpleFacets.selectFacetMethod(field, FacetMethod.FCS, mincount));
+      }
+      // UIF allowed only if UNINVERTIBLE *AND* mincount > 0
+      assertEquals(FacetMethod.FC, SimpleFacets.selectFacetMethod(field, FacetMethod.UIF, 0));
+      assertEquals(propsMatch(props, UNINVERTIBLE) ? FacetMethod.UIF : FacetMethod.FC,
+                   SimpleFacets.selectFacetMethod(field, FacetMethod.UIF, 1));
+    }
   }
 
   @Test
-  public void testTextSingleValuedDV() {
-
-    SchemaField field = new SchemaField("field", new StrField(), DOC_VALUES, null);
-
-    // default is FC, otherwise just uses the passed-in method
-
-    assertEquals(SimpleFacets.FacetMethod.FC, SimpleFacets.selectFacetMethod(field, null, 0));
-    assertEquals(SimpleFacets.FacetMethod.ENUM, SimpleFacets.selectFacetMethod(field, SimpleFacets.FacetMethod.ENUM, 0));
-    assertEquals(SimpleFacets.FacetMethod.FC, SimpleFacets.selectFacetMethod(field, SimpleFacets.FacetMethod.FC, 0));
-    assertEquals(SimpleFacets.FacetMethod.UIF, SimpleFacets.selectFacetMethod(field, SimpleFacets.FacetMethod.UIF, 0));
-    assertEquals(SimpleFacets.FacetMethod.FCS, SimpleFacets.selectFacetMethod(field, SimpleFacets.FacetMethod.FCS, 0));
-    assertEquals(SimpleFacets.FacetMethod.FC, SimpleFacets.selectFacetMethod(field, null, 1));
-    assertEquals(SimpleFacets.FacetMethod.ENUM, SimpleFacets.selectFacetMethod(field, SimpleFacets.FacetMethod.ENUM, 1));
-    assertEquals(SimpleFacets.FacetMethod.FC, SimpleFacets.selectFacetMethod(field, SimpleFacets.FacetMethod.FC, 1));
-    assertEquals(SimpleFacets.FacetMethod.UIF, SimpleFacets.selectFacetMethod(field, SimpleFacets.FacetMethod.UIF, 1));
-    assertEquals(SimpleFacets.FacetMethod.FCS, SimpleFacets.selectFacetMethod(field, SimpleFacets.FacetMethod.FCS, 1));
-
+  public void testStringSingleValuedDV() {
+
+    for (int props : Arrays.asList(DOC_VALUES ^ UNINVERTIBLE,
+                                   DOC_VALUES)) {
+      SchemaField field = new SchemaField("field", new StrField(), props, null);
+      // default is FC, otherwise just uses the passed-in method as is unless UIF...
+      for (int mincount : Arrays.asList(0, 1)) {
+        // behavior should be independent of mincount
+        assertEquals(FacetMethod.FC, SimpleFacets.selectFacetMethod(field, null, mincount));
+        assertEquals(FacetMethod.ENUM, SimpleFacets.selectFacetMethod(field, FacetMethod.ENUM, mincount));
+        assertEquals(FacetMethod.FC, SimpleFacets.selectFacetMethod(field, FacetMethod.FC, mincount));
+        assertEquals(FacetMethod.FCS, SimpleFacets.selectFacetMethod(field, FacetMethod.FCS, mincount));
+        // UIF only allowed if field is UNINVERTIBLE
+        assertEquals(propsMatch(props, UNINVERTIBLE) ? FacetMethod.UIF : FacetMethod.FCS,
+                     SimpleFacets.selectFacetMethod(field, FacetMethod.UIF, mincount));
+      }
+    }
   }
 
   @Test
-  public void testTextMultiValuedDV() {
-
-    SchemaField field = new SchemaField("field", new StrField(), DOC_VALUES ^ MULTIVALUED, null);
-
-    // default is FC, can't use FCS because of multivalues
-
-    assertEquals(SimpleFacets.FacetMethod.FC, SimpleFacets.selectFacetMethod(field, null, 0));
-    assertEquals(SimpleFacets.FacetMethod.ENUM, SimpleFacets.selectFacetMethod(field, SimpleFacets.FacetMethod.ENUM, 0));
-    assertEquals(SimpleFacets.FacetMethod.FC, SimpleFacets.selectFacetMethod(field, SimpleFacets.FacetMethod.FCS, 0));
-    assertEquals(SimpleFacets.FacetMethod.UIF, SimpleFacets.selectFacetMethod(field, SimpleFacets.FacetMethod.UIF, 0));
-    assertEquals(SimpleFacets.FacetMethod.FC, SimpleFacets.selectFacetMethod(field, SimpleFacets.FacetMethod.FC, 0));
-    assertEquals(SimpleFacets.FacetMethod.FC, SimpleFacets.selectFacetMethod(field, null, 1));
-    assertEquals(SimpleFacets.FacetMethod.ENUM, SimpleFacets.selectFacetMethod(field, SimpleFacets.FacetMethod.ENUM, 1));
-    assertEquals(SimpleFacets.FacetMethod.FC, SimpleFacets.selectFacetMethod(field, SimpleFacets.FacetMethod.FCS, 1));
-    assertEquals(SimpleFacets.FacetMethod.UIF, SimpleFacets.selectFacetMethod(field, SimpleFacets.FacetMethod.UIF, 1));
-    assertEquals(SimpleFacets.FacetMethod.FC, SimpleFacets.selectFacetMethod(field, SimpleFacets.FacetMethod.FC, 1));
-
+  public void testStringMultiValuedDV() {
+
+    for (int props : Arrays.asList(MULTIVALUED ^ DOC_VALUES ^ UNINVERTIBLE,
+                                   MULTIVALUED ^ DOC_VALUES)) {
+      SchemaField field = new SchemaField("field", new StrField(), props, null);
+      // default is FC, can't use FCS because of multivalues...
+      for (int mincount : Arrays.asList(0, 1)) {
+        // behavior should be independent of mincount
+        assertEquals(FacetMethod.FC, SimpleFacets.selectFacetMethod(field, null, mincount));
+        assertEquals(FacetMethod.ENUM, SimpleFacets.selectFacetMethod(field, FacetMethod.ENUM, mincount));
+        assertEquals(FacetMethod.FC, SimpleFacets.selectFacetMethod(field, FacetMethod.FC, mincount));
+        assertEquals(FacetMethod.FC, SimpleFacets.selectFacetMethod(field, FacetMethod.FCS, mincount));
+        // UIF only allowed if field is UNINVERTIBLE
+        assertEquals(propsMatch(props, UNINVERTIBLE) ? FacetMethod.UIF : FacetMethod.FC,
+                     SimpleFacets.selectFacetMethod(field, FacetMethod.UIF, mincount));
+      }
+    }
   }
 
   @Test
-  public void testTextSingleValuedNoDV() {
-
-    SchemaField field = new SchemaField("field", new StrField(), 0, null);
-
-    // default is FC, UIF rewrites to FCS for mincount = 0
-    // TODO should it rewrite to FC instead?
-
-    assertEquals(SimpleFacets.FacetMethod.FC, SimpleFacets.selectFacetMethod(field, null, 0));
-    assertEquals(SimpleFacets.FacetMethod.ENUM, SimpleFacets.selectFacetMethod(field, SimpleFacets.FacetMethod.ENUM, 0));
-    assertEquals(SimpleFacets.FacetMethod.FC, SimpleFacets.selectFacetMethod(field, SimpleFacets.FacetMethod.FC, 0));
-    assertEquals(SimpleFacets.FacetMethod.FCS, SimpleFacets.selectFacetMethod(field, SimpleFacets.FacetMethod.UIF, 0));
-    assertEquals(SimpleFacets.FacetMethod.FCS, SimpleFacets.selectFacetMethod(field, SimpleFacets.FacetMethod.FCS, 0));
-    assertEquals(SimpleFacets.FacetMethod.FC, SimpleFacets.selectFacetMethod(field, null, 1));
-    assertEquals(SimpleFacets.FacetMethod.ENUM, SimpleFacets.selectFacetMethod(field, SimpleFacets.FacetMethod.ENUM, 1));
-    assertEquals(SimpleFacets.FacetMethod.FC, SimpleFacets.selectFacetMethod(field, SimpleFacets.FacetMethod.FC, 1));
-    assertEquals(SimpleFacets.FacetMethod.UIF, SimpleFacets.selectFacetMethod(field, SimpleFacets.FacetMethod.UIF, 1));
-    assertEquals(SimpleFacets.FacetMethod.FCS, SimpleFacets.selectFacetMethod(field, SimpleFacets.FacetMethod.FCS, 1));
-
+  public void testStringSingleValuedNoDV() {
+
+    for (int props : Arrays.asList(0 ^ UNINVERTIBLE,
+                                   0)) {
+      SchemaField field = new SchemaField("field", new StrField(), props, null);
+      // default is FC, otherwise just uses the passed-in method as is unless UIF...
+      for (int mincount : Arrays.asList(0, 1)) {
+        // behavior should be independent of mincount
+        assertEquals(FacetMethod.FC, SimpleFacets.selectFacetMethod(field, null, mincount));
+        assertEquals(FacetMethod.ENUM, SimpleFacets.selectFacetMethod(field, FacetMethod.ENUM, mincount));
+        assertEquals(FacetMethod.FC, SimpleFacets.selectFacetMethod(field, FacetMethod.FC, mincount));
+        assertEquals(FacetMethod.FCS, SimpleFacets.selectFacetMethod(field, FacetMethod.FCS, mincount));
+      }
+      // UIF allowed only if UNINVERTIBLE *AND* mincount > 0
+      assertEquals(FacetMethod.FCS, SimpleFacets.selectFacetMethod(field, FacetMethod.UIF, 0));
+      assertEquals(propsMatch(props, UNINVERTIBLE) ? FacetMethod.UIF : FacetMethod.FCS,
+                   SimpleFacets.selectFacetMethod(field, FacetMethod.UIF, 1));
+    }
   }
 
   @Test
-  public void testTextMultiValuedNoDV() {
-
-    SchemaField field = new SchemaField("field", new StrField(), MULTIVALUED, null);
-
-    // default is FC, can't use FCS for multivalued fields, UIF rewrites to FC for mincount = 0
-
-    assertEquals(SimpleFacets.FacetMethod.FC, SimpleFacets.selectFacetMethod(field, null, 0));
-    assertEquals(SimpleFacets.FacetMethod.ENUM, SimpleFacets.selectFacetMethod(field, SimpleFacets.FacetMethod.ENUM, 0));
-    assertEquals(SimpleFacets.FacetMethod.FC, SimpleFacets.selectFacetMethod(field, SimpleFacets.FacetMethod.FCS, 0));
-    assertEquals(SimpleFacets.FacetMethod.FC, SimpleFacets.selectFacetMethod(field, SimpleFacets.FacetMethod.UIF, 0));
-    assertEquals(SimpleFacets.FacetMethod.FC, SimpleFacets.selectFacetMethod(field, SimpleFacets.FacetMethod.FC, 0));
-    assertEquals(SimpleFacets.FacetMethod.FC, SimpleFacets.selectFacetMethod(field, null, 1));
-    assertEquals(SimpleFacets.FacetMethod.ENUM, SimpleFacets.selectFacetMethod(field, SimpleFacets.FacetMethod.ENUM, 1));
-    assertEquals(SimpleFacets.FacetMethod.FC, SimpleFacets.selectFacetMethod(field, SimpleFacets.FacetMethod.FCS, 1));
-    assertEquals(SimpleFacets.FacetMethod.UIF, SimpleFacets.selectFacetMethod(field, SimpleFacets.FacetMethod.UIF, 1));
-    assertEquals(SimpleFacets.FacetMethod.FC, SimpleFacets.selectFacetMethod(field, SimpleFacets.FacetMethod.FC, 1));
-
+  public void testStringMultiValuedNoDV() {
+
+    for (int props : Arrays.asList(MULTIVALUED ^ UNINVERTIBLE,
+                                   MULTIVALUED)) {
+      SchemaField field = new SchemaField("field", new StrField(), props, null);
+      // default is FC, can't use FCS because of multivalues...
+      for (int mincount : Arrays.asList(0, 1)) {
+        // behavior should be independent of mincount
+        assertEquals(FacetMethod.FC, SimpleFacets.selectFacetMethod(field, null, mincount));
+        assertEquals(FacetMethod.ENUM, SimpleFacets.selectFacetMethod(field, FacetMethod.ENUM, mincount));
+        assertEquals(FacetMethod.FC, SimpleFacets.selectFacetMethod(field, FacetMethod.FC, mincount));
+        assertEquals(FacetMethod.FC, SimpleFacets.selectFacetMethod(field, FacetMethod.FCS, mincount));
+      }
+      // UIF allowed only if UNINVERTIBLE *AND* mincount > 0
+      assertEquals(FacetMethod.FC, SimpleFacets.selectFacetMethod(field, FacetMethod.UIF, 0));
+      assertEquals(propsMatch(props, UNINVERTIBLE) ? FacetMethod.UIF : FacetMethod.FC,
+                   SimpleFacets.selectFacetMethod(field, FacetMethod.UIF, 1));
+    }
   }
 
   @Test
   public void testBooleanDefaults() {
 
     // BoolField defaults to ENUM
-
-    SchemaField field = new SchemaField("field", new BoolField(), 0, null);
-    assertEquals(SimpleFacets.FacetMethod.ENUM, SimpleFacets.selectFacetMethod(field, null, 0));
-    assertEquals(SimpleFacets.FacetMethod.ENUM, SimpleFacets.selectFacetMethod(field, null, 1));
-
+    for (int props : Arrays.asList(0 ^ UNINVERTIBLE,
+                                   0)) {
+      SchemaField field = new SchemaField("field", new BoolField(), props, null);
+      assertEquals(SimpleFacets.FacetMethod.ENUM, SimpleFacets.selectFacetMethod(field, null, 0));
+      assertEquals(SimpleFacets.FacetMethod.ENUM, SimpleFacets.selectFacetMethod(field, null, 1));
+    }
   }
   
   @Test
   public void testPointFields() {
     // Methods other than FCS are not currently supported for PointFields
-    SchemaField field = new SchemaField("foo", new IntPointField());
-    assertEquals(SimpleFacets.FacetMethod.FCS, SimpleFacets.selectFacetMethod(field, null, 0));
-    assertEquals(SimpleFacets.FacetMethod.FCS, SimpleFacets.selectFacetMethod(field, FacetMethod.ENUM, 0));
-    assertEquals(SimpleFacets.FacetMethod.FCS, SimpleFacets.selectFacetMethod(field, FacetMethod.FC, 0));
-    assertEquals(SimpleFacets.FacetMethod.FCS, SimpleFacets.selectFacetMethod(field, FacetMethod.FCS, 0));
-    field = new SchemaField("fooMV", new IntPointField(), 0x00000200, "0"); //MultiValued
-    assertTrue(field.multiValued());
-    assertEquals(SimpleFacets.FacetMethod.FCS, SimpleFacets.selectFacetMethod(field, null, 0));
-    assertEquals(SimpleFacets.FacetMethod.FCS, SimpleFacets.selectFacetMethod(field, FacetMethod.ENUM, 0));
-    assertEquals(SimpleFacets.FacetMethod.FCS, SimpleFacets.selectFacetMethod(field, FacetMethod.FC, 0));
-    assertEquals(SimpleFacets.FacetMethod.FCS, SimpleFacets.selectFacetMethod(field, FacetMethod.FCS, 0));
+    for (int props : Arrays.asList(MULTIVALUED ^ DOC_VALUES ^ UNINVERTIBLE,
+                                   MULTIVALUED ^ DOC_VALUES,
+                                   MULTIVALUED ^ UNINVERTIBLE,
+                                   UNINVERTIBLE,
+                                   MULTIVALUED,
+                                   DOC_VALUES,
+                                   0)) {
+      SchemaField field = new SchemaField("foo", new IntPointField(), props, null);
+      for (int mincount : Arrays.asList(0, 1)) {
+        assertEquals(FacetMethod.FCS, SimpleFacets.selectFacetMethod(field, null, mincount));
+        assertEquals(FacetMethod.FCS, SimpleFacets.selectFacetMethod(field, FacetMethod.ENUM, mincount));
+        assertEquals(FacetMethod.FCS, SimpleFacets.selectFacetMethod(field, FacetMethod.FC, mincount));
+        assertEquals(FacetMethod.FCS, SimpleFacets.selectFacetMethod(field, FacetMethod.FCS, mincount));
+        assertEquals(FacetMethod.FCS, SimpleFacets.selectFacetMethod(field, FacetMethod.UIF, mincount));
+      }
+    }
   }
-
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/36ca27c3/solr/core/src/test/org/apache/solr/rest/schema/TestBulkSchemaAPI.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/rest/schema/TestBulkSchemaAPI.java b/solr/core/src/test/org/apache/solr/rest/schema/TestBulkSchemaAPI.java
index 3cc07c7..5d1dab1 100644
--- a/solr/core/src/test/org/apache/solr/rest/schema/TestBulkSchemaAPI.java
+++ b/solr/core/src/test/org/apache/solr/rest/schema/TestBulkSchemaAPI.java
@@ -390,13 +390,15 @@ public class TestBulkSchemaAPI extends RestTestBase {
         "                       'name':'a2',\n" +
         "                       'type': 'string',\n" +
         "                       'stored':true,\n" +
-        "                       'indexed':true\n" +
+        "                       'indexed':true,\n" +
+        "                       'uninvertible':true,\n" +
         "                       },\n" +
         "          'add-dynamic-field' : {\n" +
         "                       'name' :'*_lol',\n" +
         "                       'type':'string',\n" +
         "                       'stored':true,\n" +
-        "                       'indexed':true\n" +
+        "                       'indexed':true,\n" +
+        "                       'uninvertible':false,\n" +
         "                       },\n" +
         "          'add-copy-field' : {\n" +
         "                       'source' :'a1',\n" +
@@ -470,6 +472,7 @@ public class TestBulkSchemaAPI extends RestTestBase {
         "          'add-field-type' : {" +
         "                       'name' : 'myWhitespaceTxtField',\n" +
         "                       'class':'solr.TextField',\n" +
+        "                       'uninvertible':false,\n" +
         "                       'analyzer' : {'class' : 'org.apache.lucene.analysis.core.WhitespaceAnalyzer'}\n" +
         "                       },\n"+
         "          'add-field' : {\n" +
@@ -532,6 +535,7 @@ public class TestBulkSchemaAPI extends RestTestBase {
     assertEquals("string", m.get("type"));
     assertEquals(Boolean.TRUE, m.get("stored"));
     assertEquals(Boolean.TRUE, m.get("indexed"));
+    assertEquals(Boolean.TRUE, m.get("uninvertible"));
 
     m = getObj(harness,"*_lol", "dynamicFields");
     assertNotNull("field *_lol not created", m);
@@ -539,6 +543,7 @@ public class TestBulkSchemaAPI extends RestTestBase {
     assertEquals("string", m.get("type"));
     assertEquals(Boolean.TRUE, m.get("stored"));
     assertEquals(Boolean.TRUE, m.get("indexed"));
+    assertEquals(Boolean.FALSE, m.get("uninvertible"));
 
     l = getSourceCopyFields(harness, "a1");
     s = new HashSet();
@@ -579,11 +584,13 @@ public class TestBulkSchemaAPI extends RestTestBase {
     
     m = getObj(harness, "myWhitespaceTxtField", "fieldTypes");
     assertNotNull(m);
+    assertEquals(Boolean.FALSE, m.get("uninvertible"));
     assertNull(m.get("similarity")); // unspecified, expect default
 
     m = getObj(harness, "a5", "fields");
     assertNotNull("field a5 not created", m);
     assertEquals("myWhitespaceTxtField", m.get("type"));
+    assertNull(m.get("uninvertible")); // inherited, but API shouldn't return w/o explicit showDefaults
     assertFieldSimilarity("a5", BM25Similarity.class); // unspecified, expect default
 
     m = getObj(harness, "wdf_nocase", "fields");

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/36ca27c3/solr/core/src/test/org/apache/solr/rest/schema/TestFieldResource.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/rest/schema/TestFieldResource.java b/solr/core/src/test/org/apache/solr/rest/schema/TestFieldResource.java
index 4f53609..81620ed 100644
--- a/solr/core/src/test/org/apache/solr/rest/schema/TestFieldResource.java
+++ b/solr/core/src/test/org/apache/solr/rest/schema/TestFieldResource.java
@@ -23,11 +23,12 @@ public class TestFieldResource extends SolrRestletTestBase {
   public void testGetField() throws Exception {
     assertQ("/schema/fields/test_postv?indent=on&wt=xml&showDefaults=true",
             "count(/response/lst[@name='field']) = 1",
-            "count(/response/lst[@name='field']/*) = 18",
+            "count(/response/lst[@name='field']/*) = 19",
             "/response/lst[@name='field']/str[@name='name'] = 'test_postv'",
             "/response/lst[@name='field']/str[@name='type'] = 'text'",
             "/response/lst[@name='field']/bool[@name='indexed'] = 'true'",
             "/response/lst[@name='field']/bool[@name='stored'] = 'true'",
+            "/response/lst[@name='field']/bool[@name='uninvertible'] = 'true'",
             "/response/lst[@name='field']/bool[@name='docValues'] = 'false'",
             "/response/lst[@name='field']/bool[@name='termVectors'] = 'true'",
             "/response/lst[@name='field']/bool[@name='termPositions'] = 'true'",
@@ -59,6 +60,7 @@ public class TestFieldResource extends SolrRestletTestBase {
              "/field/type=='text'",
              "/field/indexed==true",
              "/field/stored==true",
+             "/field/uninvertible==true",
              "/field/docValues==false",
              "/field/termVectors==true",
              "/field/termPositions==true",

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/36ca27c3/solr/core/src/test/org/apache/solr/rest/schema/TestFieldTypeResource.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/rest/schema/TestFieldTypeResource.java b/solr/core/src/test/org/apache/solr/rest/schema/TestFieldTypeResource.java
index ea19af0..dd75658 100644
--- a/solr/core/src/test/org/apache/solr/rest/schema/TestFieldTypeResource.java
+++ b/solr/core/src/test/org/apache/solr/rest/schema/TestFieldTypeResource.java
@@ -26,12 +26,13 @@ public class TestFieldTypeResource extends SolrRestletTestBase {
     final boolean expectedDocValues = Boolean.getBoolean(NUMERIC_DOCVALUES_SYSPROP);
     assertQ("/schema/fieldtypes/float?wt=xml&showDefaults=true",
             "count(/response/lst[@name='fieldType']) = 1",
-            "count(/response/lst[@name='fieldType']/*) = 17",
+            "count(/response/lst[@name='fieldType']/*) = 18",
             "/response/lst[@name='fieldType']/str[@name='name'] = 'float'",
             "/response/lst[@name='fieldType']/str[@name='class'] = '"+expectedFloatClass+"'",
             "/response/lst[@name='fieldType']/str[@name='precisionStep'] ='0'",
             "/response/lst[@name='fieldType']/bool[@name='indexed'] = 'true'",
             "/response/lst[@name='fieldType']/bool[@name='stored'] = 'true'",
+            "/response/lst[@name='fieldType']/bool[@name='uninvertible'] = 'true'",
             "/response/lst[@name='fieldType']/bool[@name='docValues'] = '"+expectedDocValues+"'",
             "/response/lst[@name='fieldType']/bool[@name='termVectors'] = 'false'",
             "/response/lst[@name='fieldType']/bool[@name='termPositions'] = 'false'",
@@ -63,6 +64,7 @@ public class TestFieldTypeResource extends SolrRestletTestBase {
              "/fieldType/precisionStep=='0'",
              "/fieldType/indexed==true",
              "/fieldType/stored==true",
+             "/fieldType/uninvertible==true",
              "/fieldType/docValues=="+expectedDocValues,
              "/fieldType/termVectors==false",
              "/fieldType/termPositions==false",

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/36ca27c3/solr/core/src/test/org/apache/solr/schema/BadIndexSchemaTest.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/schema/BadIndexSchemaTest.java b/solr/core/src/test/org/apache/solr/schema/BadIndexSchemaTest.java
index b9dc1aa..c829c17 100644
--- a/solr/core/src/test/org/apache/solr/schema/BadIndexSchemaTest.java
+++ b/solr/core/src/test/org/apache/solr/schema/BadIndexSchemaTest.java
@@ -29,6 +29,7 @@ public class BadIndexSchemaTest extends AbstractBadConfigTestBase {
     doTest("bad-schema-not-indexed-but-norms.xml", "bad_field");
     doTest("bad-schema-not-indexed-but-tf.xml", "bad_field");
     doTest("bad-schema-not-indexed-but-pos.xml", "bad_field");
+    doTest("bad-schema-not-indexed-but-uninvertible.xml", "bad_field");
     doTest("bad-schema-omit-tf-but-not-pos.xml", "bad_field");
   }
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/36ca27c3/solr/core/src/test/org/apache/solr/schema/SchemaVersionSpecificBehaviorTest.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/schema/SchemaVersionSpecificBehaviorTest.java b/solr/core/src/test/org/apache/solr/schema/SchemaVersionSpecificBehaviorTest.java
index cf34e95..67a6291 100644
--- a/solr/core/src/test/org/apache/solr/schema/SchemaVersionSpecificBehaviorTest.java
+++ b/solr/core/src/test/org/apache/solr/schema/SchemaVersionSpecificBehaviorTest.java
@@ -64,6 +64,11 @@ public class SchemaVersionSpecificBehaviorTest extends SolrTestCaseJ4 {
           assertEquals(f + " field's type has wrong useDocValuesAsStored for ver=" + ver,
                        ( v < 1.6F ? false : true), 
                        field.useDocValuesAsStored());
+          
+          // uninvertable defaults to true (for now)
+          assertEquals(f + " field's type has wrong uninvertable for ver=" + ver,
+                       true,
+                       field.isUninvertible());
         }
 
         // regardless of version, explicit multiValued values on field or type 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/36ca27c3/solr/core/src/test/org/apache/solr/search/TestCollapseQParserPlugin.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/search/TestCollapseQParserPlugin.java b/solr/core/src/test/org/apache/solr/search/TestCollapseQParserPlugin.java
index 9f65ba5..ae72f21 100644
--- a/solr/core/src/test/org/apache/solr/search/TestCollapseQParserPlugin.java
+++ b/solr/core/src/test/org/apache/solr/search/TestCollapseQParserPlugin.java
@@ -809,6 +809,15 @@ public class TestCollapseQParserPlugin extends SolrTestCaseJ4 {
     params.add("q", "*:*");
     params.add("fq", "{!collapse field="+group+" "+optional_min_or_max+"}");
     assertQ(req(params), "*[count(//doc)=0]");
+
+    // if a field is uninvertible=false, it should behave the same as a field that is indexed=false
+    // this is currently ok on fields that don't exist on any docs in the index
+    for (String f : Arrays.asList("not_indexed_sS", "indexed_s_not_uninvert")) {
+      for (String hint : Arrays.asList("", " hint=top_fc")) {
+        assertQ(req(params("q", "*:*", "fq", "{!collapse field="+f+hint+"}"))
+                , "*[count(//doc)=0]");
+      }
+    }
   }
 
   public void testNoDocsHaveGroupField() throws Exception {
@@ -921,7 +930,8 @@ public class TestCollapseQParserPlugin extends SolrTestCaseJ4 {
 
   @Test
   public void testForNotSupportedCases() {
-    String[] doc = {"id","3", "term_s", "YYYY", "test_ii", "5000", "test_l", "100", "test_f", "200"};
+    String[] doc = {"id","3", "term_s", "YYYY", "test_ii", "5000", "test_l", "100", "test_f", "200",
+                    "not_indexed_sS", "zzz", "indexed_s_not_uninvert", "zzz"};
     assertU(adoc(doc));
     assertU(commit());
 
@@ -933,6 +943,22 @@ public class TestCollapseQParserPlugin extends SolrTestCaseJ4 {
     assertQEx("Should Fail with Bad Request", "org.apache.solr.search.SyntaxError: undefined field: \"bleh\"",
         req("q","*:*", "fq","{!collapse field=bleh}"), SolrException.ErrorCode.BAD_REQUEST);
 
+    // if a field is uninvertible=false, it should behave the same as a field that is indexed=false ...
+    for (String f : Arrays.asList("not_indexed_sS", "indexed_s_not_uninvert")) {
+      { // this currently propogates up the low level DocValues error in the common case...
+        Exception e = expectThrows(RuntimeException.class, IllegalStateException.class,
+                                    () -> h.query(req(params("q", "*:*",
+                                                             "fq", "{!collapse field="+f+"}"))));
+        assertTrue("unexpected Message: " + e.getMessage(),
+                   e.getMessage().contains("Re-index with correct docvalues type"));
+      }
+      { // ... but in the case of hint=top_fc a bare NPE gets propogated up (SOLR-12979)...
+        expectThrows(RuntimeException.class, NullPointerException.class, 
+                     () -> h.query(req(params("q", "*:*",
+                                              "fq", "{!collapse field="+f+" hint=top_fc}"))));
+      }
+      
+    }
   }
 
   @Test

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/36ca27c3/solr/core/src/test/org/apache/solr/search/facet/TestJsonFacets.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/search/facet/TestJsonFacets.java b/solr/core/src/test/org/apache/solr/search/facet/TestJsonFacets.java
index 01d29b7..e21c6d8 100644
--- a/solr/core/src/test/org/apache/solr/search/facet/TestJsonFacets.java
+++ b/solr/core/src/test/org/apache/solr/search/facet/TestJsonFacets.java
@@ -37,7 +37,9 @@ import org.apache.solr.SolrTestCaseHS;
 import org.apache.solr.common.SolrInputDocument;
 import org.apache.solr.common.params.ModifiableSolrParams;
 import org.apache.solr.common.params.SolrParams;
+import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.request.macro.MacroExpander;
+
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
 import org.junit.Test;
@@ -212,6 +214,75 @@ public class TestJsonFacets extends SolrTestCaseHS {
     client.commit();
   }
 
+  public void testBehaviorEquivilenceOfUninvertibleFalse() throws Exception {
+    Client client = Client.localClient();
+    indexSimple(client);
+
+    // regardless of the facet method (parameterized via default at test class level)
+    // faceting on an "uninvertible=false docValues=false" field is not supported.
+    //
+    // it should behave the same as any attempt (using any method) at faceting on
+    // and "indexed=false docValues=false" field...
+    for (String f : Arrays.asList("where_s_not_indexed_sS",
+                                  "where_s_multi_not_uninvert",
+                                  "where_s_single_not_uninvert")) {
+      SolrQueryRequest request = req("rows", "0", "q", "num_i:[* TO 2]", "json.facet",
+                                     "{x: {type:terms, field:'"+f+"'}}");
+      if (FacetField.FacetMethod.DEFAULT_METHOD == FacetField.FacetMethod.DVHASH
+          && !f.contains("multi")) {
+        // DVHASH is (currently) weird...
+        //
+        // it's ignored for multi valued fields -- but for single valued fields, it explicitly
+        // checks the *FieldInfos* on the reader to see if the DocVals type is ok.
+        //
+        // Which means that unlike most other facet method:xxx options, it fails hard if you try to use it
+        // on a field where no docs have been indexed (yet).
+        expectThrows(SolrException.class, () ->{
+            assertJQ(request);
+          });
+        
+      } else {
+        // In most cases, we should just get no buckets back...
+        assertJQ(request
+                 , "response/numFound==3"
+                 , "facets/count==3"
+                 , "facets/x=={buckets:[]}"
+
+                 );
+      }
+    }
+
+    // regardless of the facet method (parameterized via default at test class level)
+    // faceting on an "uninvertible=false docValues=true" field should work,
+    //
+    // it should behave equivilently to it's copyField source...
+    for (String f : Arrays.asList("where_s",
+                                  "where_s_multi_not_uninvert_dv",
+                                  "where_s_single_not_uninvert_dv")) {
+      assertJQ(req("rows", "0", "q", "num_i:[* TO 2]", "json.facet",
+                   "{x: {type:terms, field:'"+f+"'}}")
+               , "response/numFound==3"
+               , "facets/count==3"
+               , "facets/x=={buckets:[ {val:NY, count:2} , {val:NJ, count:1} ]}"
+               );
+    }
+   
+    // faceting on an "uninvertible=false docValues=false" field should be possible
+    // when using method:enum w/sort:index
+    //
+    // it should behave equivilent to it's copyField source...
+    for (String f : Arrays.asList("where_s",
+                                  "where_s_multi_not_uninvert",
+                                  "where_s_single_not_uninvert")) {
+      assertJQ(req("rows", "0", "q", "num_i:[* TO 2]", "json.facet",
+                                     "{x: {type:terms, sort:'index asc', method:enum, field:'"+f+"'}}")
+               , "response/numFound==3"
+               , "facets/count==3"
+               , "facets/x=={buckets:[ {val:NJ, count:1} , {val:NY, count:2} ]}"
+               );
+    }
+  }
+  
   /**
    * whitebox sanity checks that a shard request range facet that returns "between" or "after"
    * will cause the correct "actual_end" to be returned

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/36ca27c3/solr/solr-ref-guide/src/defining-fields.adoc
----------------------------------------------------------------------
diff --git a/solr/solr-ref-guide/src/defining-fields.adoc b/solr/solr-ref-guide/src/defining-fields.adoc
index 04c1f3e..5e6b839 100644
--- a/solr/solr-ref-guide/src/defining-fields.adoc
+++ b/solr/solr-ref-guide/src/defining-fields.adoc
@@ -56,6 +56,7 @@ Fields can have many of the same properties as field types. Properties from the
 |docValues |If true, the value of the field will be put in a column-oriented <<docvalues.adoc#docvalues,DocValues>> structure. |true or false |false
 |sortMissingFirst sortMissingLast |Control the placement of documents when a sort field is not present. |true or false |false
 |multiValued |If true, indicates that a single document might contain multiple values for this field type. |true or false |false
+|uninvertible|If true, indicates that an `indexed="true" docValues="false"` field can be "un-inverted" at query time to build up large in memory data structure to serve in place of <<docvalues.adoc#docvalues,DocValues>>.  *Defaults to true for historical reasons, but users are strongly encouraged to set this to `false` for stability and use `docValues="true"` as needed.*|true or false |true
 |omitNorms |If true, omits the norms associated with this field (this disables length normalization for the field, and saves some memory). *Defaults to true for all primitive (non-analyzed) field types, such as int, float, data, bool, and string.* Only full-text fields or fields need norms. |true or false |*
 |omitTermFreqAndPositions |If true, omits term frequency, positions, and payloads from postings for this field. This can be a performance boost for fields that don't require that information. It also reduces the storage space required for the index. Queries that rely on position that are issued on a field with this option will silently fail to find documents. *This property defaults to true for all field types that are not text fields.* |true or false |*
 |omitPositions |Similar to `omitTermFreqAndPositions` but preserves term frequency information. |true or false |*

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/36ca27c3/solr/solr-ref-guide/src/field-type-definitions-and-properties.adoc
----------------------------------------------------------------------
diff --git a/solr/solr-ref-guide/src/field-type-definitions-and-properties.adoc b/solr/solr-ref-guide/src/field-type-definitions-and-properties.adoc
index 0f64785..854132f 100644
--- a/solr/solr-ref-guide/src/field-type-definitions-and-properties.adoc
+++ b/solr/solr-ref-guide/src/field-type-definitions-and-properties.adoc
@@ -130,6 +130,7 @@ The default values for each property depend on the underlying `FieldType` class,
 |docValues |If true, the value of the field will be put in a column-oriented <<docvalues.adoc#docvalues,DocValues>> structure. |true or false |false
 |sortMissingFirst sortMissingLast |Control the placement of documents when a sort field is not present. |true or false |false
 |multiValued |If true, indicates that a single document might contain multiple values for this field type. |true or false |false
+|uninvertible|If true, indicates that an `indexed="true" docValues="false"` field can be "un-inverted" at query time to build up large in memory data structure to serve in place of <<docvalues.adoc#docvalues,DocValues>>.  *Defaults to true for historical reasons, but users are strongly encouraged to set this to `false` for stability and use `docValues="true"` as needed.*|true or false |true
 |omitNorms |If true, omits the norms associated with this field (this disables length normalization for the field, and saves some memory). *Defaults to true for all primitive (non-analyzed) field types, such as int, float, data, bool, and string.* Only full-text fields or fields need norms. |true or false |*
 |omitTermFreqAndPositions |If true, omits term frequency, positions, and payloads from postings for this field. This can be a performance boost for fields that don't require that information. It also reduces the storage space required for the index. Queries that rely on position that are issued on a field with this option will silently fail to find documents. *This property defaults to true for all field types that are not text fields.* |true or false |*
 |omitPositions |Similar to `omitTermFreqAndPositions` but preserves term frequency information. |true or false |*


Mime
View raw message