You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by mi...@apache.org on 2014/01/28 18:07:20 UTC

svn commit: r1562127 - in /lucene/dev/branches/lucene5376/lucene: core/src/java/org/apache/lucene/search/ demo/src/java/org/apache/lucene/demo/facet/ facet/src/java/org/apache/lucene/facet/ facet/src/java/org/apache/lucene/facet/range/ facet/src/test/o...

Author: mikemccand
Date: Tue Jan 28 17:07:19 2014
New Revision: 1562127

URL: http://svn.apache.org/r1562127
Log:
LUCENE-5418: fix drill-down & drill-sideways to use Bits interface when applying a Filter

Added:
    lucene/dev/branches/lucene5376/lucene/facet/src/java/org/apache/lucene/facet/range/SlowBitsDocIdSetIterator.java   (with props)
Modified:
    lucene/dev/branches/lucene5376/lucene/core/src/java/org/apache/lucene/search/DocIdSet.java
    lucene/dev/branches/lucene5376/lucene/core/src/java/org/apache/lucene/search/FilteredQuery.java
    lucene/dev/branches/lucene5376/lucene/demo/src/java/org/apache/lucene/demo/facet/DistanceFacetsExample.java
    lucene/dev/branches/lucene5376/lucene/facet/src/java/org/apache/lucene/facet/DrillDownQuery.java
    lucene/dev/branches/lucene5376/lucene/facet/src/java/org/apache/lucene/facet/DrillSideways.java
    lucene/dev/branches/lucene5376/lucene/facet/src/java/org/apache/lucene/facet/DrillSidewaysQuery.java
    lucene/dev/branches/lucene5376/lucene/facet/src/java/org/apache/lucene/facet/DrillSidewaysScorer.java
    lucene/dev/branches/lucene5376/lucene/facet/src/java/org/apache/lucene/facet/range/DoubleRange.java
    lucene/dev/branches/lucene5376/lucene/facet/src/java/org/apache/lucene/facet/range/LongRange.java
    lucene/dev/branches/lucene5376/lucene/facet/src/java/org/apache/lucene/facet/range/Range.java
    lucene/dev/branches/lucene5376/lucene/facet/src/test/org/apache/lucene/facet/range/TestRangeFacetCounts.java

Modified: lucene/dev/branches/lucene5376/lucene/core/src/java/org/apache/lucene/search/DocIdSet.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/lucene5376/lucene/core/src/java/org/apache/lucene/search/DocIdSet.java?rev=1562127&r1=1562126&r2=1562127&view=diff
==============================================================================
--- lucene/dev/branches/lucene5376/lucene/core/src/java/org/apache/lucene/search/DocIdSet.java (original)
+++ lucene/dev/branches/lucene5376/lucene/core/src/java/org/apache/lucene/search/DocIdSet.java Tue Jan 28 17:07:19 2014
@@ -31,6 +31,13 @@ public abstract class DocIdSet {
    * are no docs that match. */
   public abstract DocIdSetIterator iterator() throws IOException;
 
+  // TODO: somehow this class should express the cost of
+  // iteration vs the cost of random access Bits; for
+  // expensive Filters (e.g. distance < 1 km) we should use
+  // bits() after all other Query/Filters have matched, but
+  // this is the opposite of what bits() is for now
+  // (down-low filtering)
+
   /** Optionally provides a {@link Bits} interface for random access
    * to matching documents.
    * @return {@code null}, if this {@code DocIdSet} does not support random access.

Modified: lucene/dev/branches/lucene5376/lucene/core/src/java/org/apache/lucene/search/FilteredQuery.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/lucene5376/lucene/core/src/java/org/apache/lucene/search/FilteredQuery.java?rev=1562127&r1=1562126&r2=1562127&view=diff
==============================================================================
--- lucene/dev/branches/lucene5376/lucene/core/src/java/org/apache/lucene/search/FilteredQuery.java (original)
+++ lucene/dev/branches/lucene5376/lucene/core/src/java/org/apache/lucene/search/FilteredQuery.java Tue Jan 28 17:07:19 2014
@@ -50,7 +50,7 @@ public class FilteredQuery extends Query
    * @param query  Query to be filtered, cannot be <code>null</code>.
    * @param filter Filter to apply to query results, cannot be <code>null</code>.
    */
-  public FilteredQuery (Query query, Filter filter) {
+  public FilteredQuery(Query query, Filter filter) {
     this(query, filter, RANDOM_ACCESS_FILTER_STRATEGY);
   }
   
@@ -63,7 +63,7 @@ public class FilteredQuery extends Query
    * 
    * @see FilterStrategy
    */
-  public FilteredQuery (Query query, Filter filter, FilterStrategy strategy) {
+  public FilteredQuery(Query query, Filter filter, FilterStrategy strategy) {
     if (query == null || filter == null)
       throw new IllegalArgumentException("Query and filter cannot be null.");
     if (strategy == null)
@@ -118,7 +118,9 @@ public class FilteredQuery extends Query
 
       // return this query
       @Override
-      public Query getQuery() { return FilteredQuery.this; }
+      public Query getQuery() {
+        return FilteredQuery.this;
+      }
 
       // return a filtering scorer
       @Override
@@ -130,8 +132,8 @@ public class FilteredQuery extends Query
           // this means the filter does not accept any documents.
           return null;
         }
+
         return strategy.filteredScorer(context, scoreDocsInOrder, topScorer, weight, filterDocIdSet);
-        
       }
     };
   }
@@ -183,14 +185,12 @@ public class FilteredQuery extends Query
     
     @Override
     public int advance(int target) throws IOException {
-      
       int doc = scorer.advance(target);
       if (doc != Scorer.NO_MORE_DOCS && !filterbits.get(doc)) {
         return scorerDoc = nextDoc();
       } else {
         return scorerDoc = doc;
       }
-      
     }
 
     @Override
@@ -303,7 +303,9 @@ public class FilteredQuery extends Query
     }
     
     @Override
-    public final int freq() throws IOException { return scorer.freq(); }
+    public final int freq() throws IOException {
+      return scorer.freq();
+    }
     
     @Override
     public final Collection<ChildScorer> getChildren() {
@@ -527,7 +529,7 @@ public class FilteredQuery extends Query
       
       final Bits filterAcceptDocs = docIdSet.bits();
       // force if RA is requested
-      final boolean useRandomAccess = (filterAcceptDocs != null && (useRandomAccess(filterAcceptDocs, firstFilterDoc)));
+      final boolean useRandomAccess = filterAcceptDocs != null && useRandomAccess(filterAcceptDocs, firstFilterDoc);
       if (useRandomAccess) {
         // if we are using random access, we return the inner scorer, just with other acceptDocs
         return weight.scorer(context, scoreDocsInOrder, topScorer, filterAcceptDocs);

Modified: lucene/dev/branches/lucene5376/lucene/demo/src/java/org/apache/lucene/demo/facet/DistanceFacetsExample.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/lucene5376/lucene/demo/src/java/org/apache/lucene/demo/facet/DistanceFacetsExample.java?rev=1562127&r1=1562126&r2=1562127&view=diff
==============================================================================
--- lucene/dev/branches/lucene5376/lucene/demo/src/java/org/apache/lucene/demo/facet/DistanceFacetsExample.java (original)
+++ lucene/dev/branches/lucene5376/lucene/demo/src/java/org/apache/lucene/demo/facet/DistanceFacetsExample.java Tue Jan 28 17:07:19 2014
@@ -29,16 +29,18 @@ import org.apache.lucene.expressions.Exp
 import org.apache.lucene.expressions.SimpleBindings;
 import org.apache.lucene.expressions.js.JavascriptCompiler;
 import org.apache.lucene.facet.DrillDownQuery;
+import org.apache.lucene.facet.DrillSideways;
 import org.apache.lucene.facet.FacetResult;
 import org.apache.lucene.facet.Facets;
 import org.apache.lucene.facet.FacetsCollector;
+import org.apache.lucene.facet.FacetsConfig;
 import org.apache.lucene.facet.range.DoubleRange;
 import org.apache.lucene.facet.range.DoubleRangeFacetCounts;
+import org.apache.lucene.facet.taxonomy.TaxonomyReader;
 import org.apache.lucene.index.DirectoryReader;
 import org.apache.lucene.index.IndexWriter;
 import org.apache.lucene.index.IndexWriterConfig;
 import org.apache.lucene.queries.function.ValueSource;
-import org.apache.lucene.search.ConstantScoreQuery;
 import org.apache.lucene.search.IndexSearcher;
 import org.apache.lucene.search.MatchAllDocsQuery;
 import org.apache.lucene.search.SortField;
@@ -59,6 +61,7 @@ public class DistanceFacetsExample imple
 
   private final Directory indexDir = new RAMDirectory();
   private IndexSearcher searcher;
+  private final FacetsConfig config = new FacetsConfig();
 
   /** Empty constructor */
   public DistanceFacetsExample() {}
@@ -127,10 +130,16 @@ public class DistanceFacetsExample imple
     // Passing no baseQuery means we drill down on all
     // documents ("browse only"):
     DrillDownQuery q = new DrillDownQuery(null);
-
-    q.add("field", new ConstantScoreQuery(range.getFilter(getDistanceValueSource())));
-
-    return searcher.search(q, 10);
+    final ValueSource vs = getDistanceValueSource();
+    q.add("field", range.getFilter(vs));
+    DrillSideways ds = new DrillSideways(searcher, config, (TaxonomyReader) null) {
+        @Override
+        protected Facets buildFacetsResult(FacetsCollector drillDowns, FacetsCollector[] drillSideways, String[] drillSidewaysDims) throws IOException {        
+          assert drillSideways.length == 1;
+          return new DoubleRangeFacetCounts("field", vs, drillSideways[0], ONE_KM, TWO_KM, FIVE_KM, TEN_KM);
+        }
+      };
+    return ds.search(q, 10).hits;
   }
 
   @Override

Modified: lucene/dev/branches/lucene5376/lucene/facet/src/java/org/apache/lucene/facet/DrillDownQuery.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/lucene5376/lucene/facet/src/java/org/apache/lucene/facet/DrillDownQuery.java?rev=1562127&r1=1562126&r2=1562127&view=diff
==============================================================================
--- lucene/dev/branches/lucene5376/lucene/facet/src/java/org/apache/lucene/facet/DrillDownQuery.java (original)
+++ lucene/dev/branches/lucene5376/lucene/facet/src/java/org/apache/lucene/facet/DrillDownQuery.java Tue Jan 28 17:07:19 2014
@@ -18,22 +18,20 @@ package org.apache.lucene.facet;
  */
 
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 
-import org.apache.lucene.facet.range.DoubleRangeFacetCounts;
-import org.apache.lucene.facet.range.LongRangeFacetCounts;
 import org.apache.lucene.index.IndexReader;
 import org.apache.lucene.index.Term;
-import org.apache.lucene.search.BooleanClause;
 import org.apache.lucene.search.BooleanClause.Occur;
+import org.apache.lucene.search.BooleanClause;
 import org.apache.lucene.search.BooleanQuery;
 import org.apache.lucene.search.ConstantScoreQuery;
 import org.apache.lucene.search.Filter;
 import org.apache.lucene.search.FilteredQuery;
 import org.apache.lucene.search.MatchAllDocsQuery;
-import org.apache.lucene.search.NumericRangeQuery;
 import org.apache.lucene.search.Query;
 import org.apache.lucene.search.TermQuery;
 
@@ -86,7 +84,7 @@ public final class DrillDownQuery extend
 
   /** Used by DrillSideways */
   DrillDownQuery(FacetsConfig config, Query baseQuery, List<Query> clauses, Map<String,Integer> drillDownDims) {
-    this.query = new BooleanQuery(true);
+    query = new BooleanQuery(true);
     if (baseQuery != null) {
       query.add(baseQuery, Occur.MUST);      
     }
@@ -155,11 +153,12 @@ public final class DrillDownQuery extend
 
   /** Expert: add a custom drill-down subQuery.  Use this
    *  when you have a separate way to drill-down on the
-   *  dimension than the indexed facet ordinals (for
-   *  example, use a {@link NumericRangeQuery} to drill down
-   *  after {@link LongRangeFacetCounts} or {@link DoubleRangeFacetCounts}. */
+   *  dimension than the indexed facet ordinals. */
   public void add(String dim, Query subQuery) {
 
+    if (drillDownDims.containsKey(dim)) {
+      throw new IllegalArgumentException("dimension \"" + dim + "\" already has a drill-down");
+    }
     // TODO: we should use FilteredQuery?
 
     // So scores of the drill-down query don't have an
@@ -172,6 +171,40 @@ public final class DrillDownQuery extend
     drillDownDims.put(dim, drillDownDims.size());
   }
 
+  /** Expert: add a custom drill-down Filter, e.g. when
+   *  drilling down after range faceting. */
+  public void add(String dim, Filter subFilter) {
+
+    if (drillDownDims.containsKey(dim)) {
+      throw new IllegalArgumentException("dimension \"" + dim + "\" already has a drill-down");
+    }
+
+    // TODO: we should use FilteredQuery?
+
+    // So scores of the drill-down query don't have an
+    // effect:
+    final ConstantScoreQuery drillDownQuery = new ConstantScoreQuery(subFilter);
+    drillDownQuery.setBoost(0.0f);
+
+    query.add(drillDownQuery, Occur.MUST);
+
+    drillDownDims.put(dim, drillDownDims.size());
+  }
+
+  static Filter getFilter(Query query) {
+    if (query instanceof ConstantScoreQuery) {
+      ConstantScoreQuery csq = (ConstantScoreQuery) query;
+      Filter filter = csq.getFilter();
+      if (filter != null) {
+        return filter;
+      } else {
+        return getFilter(csq.getQuery());
+      }
+    } else {
+      return null;
+    }
+  }
+
   @Override
   public DrillDownQuery clone() {
     return new DrillDownQuery(config, query, drillDownDims);
@@ -199,7 +232,63 @@ public final class DrillDownQuery extend
     if (query.clauses().size() == 0) {
       return new MatchAllDocsQuery();
     }
-    return query;
+
+    List<Filter> filters = new ArrayList<Filter>();
+    List<Query> queries = new ArrayList<Query>();
+    List<BooleanClause> clauses = query.clauses();
+    Query baseQuery;
+    int startIndex;
+    if (drillDownDims.size() == query.clauses().size()) {
+      baseQuery = new MatchAllDocsQuery();
+      startIndex = 0;
+    } else {
+      baseQuery = clauses.get(0).getQuery();
+      startIndex = 1;
+    }
+
+    for(int i=startIndex;i<clauses.size();i++) {
+      BooleanClause clause = clauses.get(i);
+      Query queryClause = clause.getQuery();
+      Filter filter = getFilter(queryClause);
+      if (filter != null) {
+        filters.add(filter);
+      } else {
+        queries.add(queryClause);
+      }
+    }
+
+    if (filters.isEmpty()) {
+      return query;
+    } else {
+      // Wrap all filters using FilteredQuery
+      
+      // TODO: this is hackish; we need to do it because
+      // BooleanQuery can't be trusted to handle the
+      // "expensive filter" case.  Really, each Filter should
+      // know its cost and we should take that more
+      // carefully into account when picking the right
+      // strategy/optimization:
+      Query wrapped;
+      if (queries.isEmpty()) {
+        wrapped = baseQuery;
+      } else {
+        // disable coord
+        BooleanQuery wrappedBQ = new BooleanQuery(true);
+        if ((baseQuery instanceof MatchAllDocsQuery) == false) {
+          wrappedBQ.add(baseQuery, BooleanClause.Occur.MUST);
+        }
+        for(Query q : queries) {
+          wrappedBQ.add(q, BooleanClause.Occur.MUST);
+        }
+        wrapped = wrappedBQ;
+      }
+
+      for(Filter filter : filters) {
+        wrapped = new FilteredQuery(wrapped, filter, FilteredQuery.QUERY_FIRST_FILTER_STRATEGY);
+      }
+
+      return wrapped;
+    }
   }
 
   @Override

Modified: lucene/dev/branches/lucene5376/lucene/facet/src/java/org/apache/lucene/facet/DrillSideways.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/lucene5376/lucene/facet/src/java/org/apache/lucene/facet/DrillSideways.java?rev=1562127&r1=1562126&r2=1562127&view=diff
==============================================================================
--- lucene/dev/branches/lucene5376/lucene/facet/src/java/org/apache/lucene/facet/DrillSideways.java (original)
+++ lucene/dev/branches/lucene5376/lucene/facet/src/java/org/apache/lucene/facet/DrillSideways.java Tue Jan 28 17:07:19 2014
@@ -182,8 +182,8 @@ public class DrillSideways {
    * drill down and sideways counts.
    */
   public DrillSidewaysResult search(DrillDownQuery query,
-                                          Filter filter, FieldDoc after, int topN, Sort sort, boolean doDocScores,
-                                          boolean doMaxScore) throws IOException {
+                                    Filter filter, FieldDoc after, int topN, Sort sort, boolean doDocScores,
+                                    boolean doMaxScore) throws IOException {
     if (filter != null) {
       query = new DrillDownQuery(config, filter, query);
     }

Modified: lucene/dev/branches/lucene5376/lucene/facet/src/java/org/apache/lucene/facet/DrillSidewaysQuery.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/lucene5376/lucene/facet/src/java/org/apache/lucene/facet/DrillSidewaysQuery.java?rev=1562127&r1=1562126&r2=1562127&view=diff
==============================================================================
--- lucene/dev/branches/lucene5376/lucene/facet/src/java/org/apache/lucene/facet/DrillSidewaysQuery.java (original)
+++ lucene/dev/branches/lucene5376/lucene/facet/src/java/org/apache/lucene/facet/DrillSidewaysQuery.java Tue Jan 28 17:07:19 2014
@@ -22,8 +22,10 @@ import java.util.Arrays;
 import org.apache.lucene.index.AtomicReaderContext;
 import org.apache.lucene.index.IndexReader;
 import org.apache.lucene.search.Collector;
+import org.apache.lucene.search.DocIdSet;
 import org.apache.lucene.search.DocIdSetIterator;
 import org.apache.lucene.search.Explanation;
+import org.apache.lucene.search.Filter;
 import org.apache.lucene.search.IndexSearcher;
 import org.apache.lucene.search.Query;
 import org.apache.lucene.search.Scorer;
@@ -73,9 +75,17 @@ class DrillSidewaysQuery extends Query {
   @Override
   public Weight createWeight(IndexSearcher searcher) throws IOException {
     final Weight baseWeight = baseQuery.createWeight(searcher);
-    final Weight[] drillDownWeights = new Weight[drillDownQueries.length];
+    final Object[] drillDowns = new Object[drillDownQueries.length];
     for(int dim=0;dim<drillDownQueries.length;dim++) {
-      drillDownWeights[dim] = searcher.rewrite(drillDownQueries[dim]).createWeight(searcher);
+      Query query = drillDownQueries[dim];
+      Filter filter = DrillDownQuery.getFilter(query);
+      if (filter != null) {
+        drillDowns[dim] = filter;
+      } else {
+        // TODO: would be nice if we could say "we will do no
+        // scoring" here....
+        drillDowns[dim] = searcher.rewrite(query).createWeight(searcher);
+      }
     }
 
     return new Weight() {
@@ -110,30 +120,69 @@ class DrillSidewaysQuery extends Query {
       public Scorer scorer(AtomicReaderContext context, boolean scoreDocsInOrder,
                            boolean topScorer, Bits acceptDocs) throws IOException {
 
-        DrillSidewaysScorer.DocsAndCost[] dims = new DrillSidewaysScorer.DocsAndCost[drillDownWeights.length];
+        // TODO: it could be better if we take acceptDocs
+        // into account instead of baseScorer?
+        Scorer baseScorer = baseWeight.scorer(context, scoreDocsInOrder, false, acceptDocs);
+
+        DrillSidewaysScorer.DocsAndCost[] dims = new DrillSidewaysScorer.DocsAndCost[drillDowns.length];
         int nullCount = 0;
         for(int dim=0;dim<dims.length;dim++) {
           dims[dim] = new DrillSidewaysScorer.DocsAndCost();
           dims[dim].sidewaysCollector = drillSidewaysCollectors[dim];
-          DocIdSetIterator disi = drillDownWeights[dim].scorer(context, true, false, null);
-          if (disi == null) {
-            nullCount++;
-            continue;
+          if (drillDowns[dim] instanceof Filter) {
+            // Pass null for acceptDocs because we already
+            // passed it to baseScorer and baseScorer is
+            // MUST'd here
+            DocIdSet dis = ((Filter) drillDowns[dim]).getDocIdSet(context, null);
+
+            Bits bits = dis.bits();
+            if (bits != null) {
+              // TODO: this logic is too naive: the
+              // existence of bits() in DIS today means
+              // either "I'm a cheap FixedBitSet so apply me down
+              // low as you decode the postings" or "I'm so
+              // horribly expensive so apply me after all
+              // other Query/Filter clauses pass"
+
+              // Filter supports random access; use that to
+              // prevent .advance() on costly filters:
+              dims[dim].bits = bits;
+
+              // TODO: Filter needs to express its expected
+              // cost somehow, before pulling the iterator;
+              // we should use that here to set the order to
+              // check the filters:
+
+            } else {
+              DocIdSetIterator disi = dis.iterator();
+              if (disi == null) {
+                nullCount++;
+                continue;
+              }
+              dims[dim].disi = disi;
+            }
+          } else {
+            DocIdSetIterator disi = ((Weight) drillDowns[dim]).scorer(context, true, false, null);
+            if (disi == null) {
+              nullCount++;
+              continue;
+            }
+            dims[dim].disi = disi;
           }
-          dims[dim].disi = disi;
         }
 
-        if (nullCount > 1 || (nullCount == 1 && dims.length == 1)) {
+        // If more than one dim has no matches, then there
+        // are no hits nor drill-sideways counts.  Or, if we
+        // have only one dim and that dim has no matches,
+        // same thing.
+        //if (nullCount > 1 || (nullCount == 1 && dims.length == 1)) {
+        if (nullCount > 1) {
           return null;
         }
 
         // Sort drill-downs by most restrictive first:
         Arrays.sort(dims);
 
-        // TODO: it could be better if we take acceptDocs
-        // into account instead of baseScorer?
-        Scorer baseScorer = baseWeight.scorer(context, scoreDocsInOrder, false, acceptDocs);
-
         if (baseScorer == null) {
           return null;
         }

Modified: lucene/dev/branches/lucene5376/lucene/facet/src/java/org/apache/lucene/facet/DrillSidewaysScorer.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/lucene5376/lucene/facet/src/java/org/apache/lucene/facet/DrillSidewaysScorer.java?rev=1562127&r1=1562126&r2=1562127&view=diff
==============================================================================
--- lucene/dev/branches/lucene5376/lucene/facet/src/java/org/apache/lucene/facet/DrillSidewaysScorer.java (original)
+++ lucene/dev/branches/lucene5376/lucene/facet/src/java/org/apache/lucene/facet/DrillSidewaysScorer.java Tue Jan 28 17:07:19 2014
@@ -26,6 +26,7 @@ import org.apache.lucene.search.Collecto
 import org.apache.lucene.search.DocIdSetIterator;
 import org.apache.lucene.search.Scorer;
 import org.apache.lucene.search.Weight;
+import org.apache.lucene.util.Bits;
 import org.apache.lucene.util.FixedBitSet;
 
 class DrillSidewaysScorer extends Scorer {
@@ -82,20 +83,39 @@ class DrillSidewaysScorer extends Scorer
 
     // Position all scorers to their first matching doc:
     baseScorer.nextDoc();
+    int numBits = 0;
     for (DocsAndCost dim : dims) {
-      dim.disi.nextDoc();
+      if (dim.disi != null) {
+        dim.disi.nextDoc();
+      } else if (dim.bits != null) {
+        numBits++;
+      }
     }
 
     final int numDims = dims.length;
 
-    DocIdSetIterator[] disis = new DocIdSetIterator[numDims];
-    Collector[] sidewaysCollectors = new Collector[numDims];
+    Bits[] bits = new Bits[numBits];
+    Collector[] bitsSidewaysCollectors = new Collector[numBits];
+
+    DocIdSetIterator[] disis = new DocIdSetIterator[numDims-numBits];
+    Collector[] sidewaysCollectors = new Collector[numDims-numBits];
     long drillDownCost = 0;
+    int disiUpto = 0;
+    int bitsUpto = 0;
     for (int dim=0;dim<numDims;dim++) {
       DocIdSetIterator disi = dims[dim].disi;
-      disis[dim] = disi;
-      sidewaysCollectors[dim] = dims[dim].sidewaysCollector;
-      drillDownCost += disi.cost();
+      if (dims[dim].bits == null) {
+        disis[disiUpto] = disi;
+        sidewaysCollectors[disiUpto] = dims[dim].sidewaysCollector;
+        disiUpto++;
+        if (disi != null) {
+          drillDownCost += disi.cost();
+        }
+      } else {
+        bits[bitsUpto] = dims[dim].bits;
+        bitsSidewaysCollectors[bitsUpto] = dims[dim].sidewaysCollector;
+        bitsUpto++;
+      }
     }
 
     long baseQueryCost = baseScorer.cost();
@@ -110,10 +130,10 @@ class DrillSidewaysScorer extends Scorer
     }
     */
 
-    if (scoreSubDocsAtOnce || baseQueryCost < drillDownCost/10) {
-      //System.out.println("baseAdvance");
-      doBaseAdvanceScoring(collector, disis, sidewaysCollectors);
-    } else if (numDims > 1 && dims[1].disi.cost() < baseQueryCost/10) {
+    if (bitsUpto > 0 || scoreSubDocsAtOnce || baseQueryCost < drillDownCost/10) {
+      //System.out.println("queryFirst");
+      doQueryFirstScoring(collector, disis, sidewaysCollectors, bits, bitsSidewaysCollectors);
+    } else if (numDims > 1 && (dims[1].disi == null || dims[1].disi.cost() < baseQueryCost/10)) {
       //System.out.println("drillDownAdvance");
       doDrillDownAdvanceScoring(collector, disis, sidewaysCollectors);
     } else {
@@ -127,32 +147,55 @@ class DrillSidewaysScorer extends Scorer
    *  (i.e., like BooleanScorer2, not BooleanScorer).  In
    *  this case we just .next() on base and .advance() on
    *  the dim filters. */ 
-  private void doBaseAdvanceScoring(Collector collector, DocIdSetIterator[] disis, Collector[] sidewaysCollectors) throws IOException {
+  private void doQueryFirstScoring(Collector collector, DocIdSetIterator[] disis, Collector[] sidewaysCollectors,
+                                   Bits[] bits, Collector[] bitsSidewaysCollectors) throws IOException {
     //if (DEBUG) {
-    //  System.out.println("  doBaseAdvanceScoring");
+    //  System.out.println("  doQueryFirstScoring");
     //}
     int docID = baseScorer.docID();
 
-    final int numDims = dims.length;
-
     nextDoc: while (docID != NO_MORE_DOCS) {
-      int failedDim = -1;
-      for (int dim=0;dim<numDims;dim++) {
+      Collector failedCollector = null;
+      for (int i=0;i<disis.length;i++) {
         // TODO: should we sort this 2nd dimension of
         // docsEnums from most frequent to least?
-        DocIdSetIterator disi = disis[dim];
-        if (disi.docID() < docID) {
-          disi.advance(docID);
+        DocIdSetIterator disi = disis[i];
+        if (disi != null) {
+          if (disi.docID() < docID) {
+            disi.advance(docID);
+          }
+          if (disi.docID() > docID) {
+            if (failedCollector != null) {
+              // More than one dim fails on this document, so
+              // it's neither a hit nor a near-miss; move to
+              // next doc:
+              docID = baseScorer.nextDoc();
+              continue nextDoc;
+            } else {
+              failedCollector = sidewaysCollectors[i];
+            }
+          }
         }
-        if (disi.docID() > docID) {
-          if (failedDim != -1) {
+      }
+
+      // TODO: for the "non-costly Bits" we really should
+      // have passed them down as acceptDocs, but
+      // unfortunately we cannot distinguish today betwen
+      // "bits() is so costly that you should apply it last"
+      // from "bits() is so cheap that you should apply it
+      // everywhere down low"
+
+      // Fold in Filter Bits last, since they may be costly:
+      for(int i=0;i<bits.length;i++) {
+        if (bits[i].get(docID) == false) {
+          if (failedCollector != null) {
             // More than one dim fails on this document, so
             // it's neither a hit nor a near-miss; move to
             // next doc:
             docID = baseScorer.nextDoc();
             continue nextDoc;
           } else {
-            failedDim = dim;
+            failedCollector = bitsSidewaysCollectors[i];
           }
         }
       }
@@ -163,12 +206,12 @@ class DrillSidewaysScorer extends Scorer
       // daat here:
       collectScore = baseScorer.score();
 
-      if (failedDim == -1) {
+      if (failedCollector == null) {
         // Hit passed all filters, so it's "real":
-        collectHit(collector, sidewaysCollectors);
+        collectHit(collector, sidewaysCollectors, bitsSidewaysCollectors);
       } else {
         // Hit missed exactly one filter:
-        collectNearMiss(sidewaysCollectors, failedDim);
+        collectNearMiss(failedCollector);
       }
 
       docID = baseScorer.nextDoc();
@@ -207,60 +250,64 @@ class DrillSidewaysScorer extends Scorer
       //  System.out.println("  dim0");
       //}
       DocIdSetIterator disi = disis[0];
-      int docID = disi.docID();
-      while (docID < nextChunkStart) {
-        int slot = docID & MASK;
+      if (disi != null) {
+        int docID = disi.docID();
+        while (docID < nextChunkStart) {
+          int slot = docID & MASK;
 
-        if (docIDs[slot] != docID) {
-          seen.set(slot);
-          // Mark slot as valid:
-          //if (DEBUG) {
-          //  System.out.println("    set docID=" + docID + " id=" + context.reader().document(docID).get("id"));
-          //}
-          docIDs[slot] = docID;
-          missingDims[slot] = 1;
-          counts[slot] = 1;
-        }
+          if (docIDs[slot] != docID) {
+            seen.set(slot);
+            // Mark slot as valid:
+            //if (DEBUG) {
+            //  System.out.println("    set docID=" + docID + " id=" + context.reader().document(docID).get("id"));
+            //}
+            docIDs[slot] = docID;
+            missingDims[slot] = 1;
+            counts[slot] = 1;
+          }
 
-        docID = disi.nextDoc();
+          docID = disi.nextDoc();
+        }
       }
-
+      
       // Second dim:
       //if (DEBUG) {
       //  System.out.println("  dim1");
       //}
       disi = disis[1];
-      docID = disi.docID();
-      while (docID < nextChunkStart) {
-        int slot = docID & MASK;
+      if (disi != null) {
+        int docID = disi.docID();
+        while (docID < nextChunkStart) {
+          int slot = docID & MASK;
 
-        if (docIDs[slot] != docID) {
-          // Mark slot as valid:
-          seen.set(slot);
-          //if (DEBUG) {
-          //  System.out.println("    set docID=" + docID + " missingDim=0 id=" + context.reader().document(docID).get("id"));
-          //}
-          docIDs[slot] = docID;
-          missingDims[slot] = 0;
-          counts[slot] = 1;
-        } else {
-          // TODO: single-valued dims will always be true
-          // below; we could somehow specialize
-          if (missingDims[slot] >= 1) {
-            missingDims[slot] = 2;
-            counts[slot] = 2;
+          if (docIDs[slot] != docID) {
+            // Mark slot as valid:
+            seen.set(slot);
             //if (DEBUG) {
-            //  System.out.println("    set docID=" + docID + " missingDim=2 id=" + context.reader().document(docID).get("id"));
+            //  System.out.println("    set docID=" + docID + " missingDim=0 id=" + context.reader().document(docID).get("id"));
             //}
-          } else {
+            docIDs[slot] = docID;
+            missingDims[slot] = 0;
             counts[slot] = 1;
-            //if (DEBUG) {
-            //  System.out.println("    set docID=" + docID + " missingDim=" + missingDims[slot] + " id=" + context.reader().document(docID).get("id"));
-            //}
+          } else {
+            // TODO: single-valued dims will always be true
+            // below; we could somehow specialize
+            if (missingDims[slot] >= 1) {
+              missingDims[slot] = 2;
+              counts[slot] = 2;
+              //if (DEBUG) {
+              //  System.out.println("    set docID=" + docID + " missingDim=2 id=" + context.reader().document(docID).get("id"));
+              //}
+            } else {
+              counts[slot] = 1;
+              //if (DEBUG) {
+              //  System.out.println("    set docID=" + docID + " missingDim=" + missingDims[slot] + " id=" + context.reader().document(docID).get("id"));
+              //}
+            }
           }
-        }
 
-        docID = disi.nextDoc();
+          docID = disi.nextDoc();
+        }
       }
 
       // After this we can "upgrade" to conjunction, because
@@ -318,28 +365,30 @@ class DrillSidewaysScorer extends Scorer
         //  System.out.println("  dim=" + dim + " [" + dims[dim].dim + "]");
         //}
         disi = disis[dim];
-        docID = disi.docID();
-        while (docID < nextChunkStart) {
-          int slot = docID & MASK;
-          if (docIDs[slot] == docID && counts[slot] >= dim) {
-            // TODO: single-valued dims will always be true
-            // below; we could somehow specialize
-            if (missingDims[slot] >= dim) {
-              //if (DEBUG) {
-              //  System.out.println("    set docID=" + docID + " count=" + (dim+2));
-              //}
-              missingDims[slot] = dim+1;
-              counts[slot] = dim+2;
-            } else {
-              //if (DEBUG) {
-              //  System.out.println("    set docID=" + docID + " missing count=" + (dim+1));
-              //}
-              counts[slot] = dim+1;
+        if (disi != null) {
+          int docID = disi.docID();
+          while (docID < nextChunkStart) {
+            int slot = docID & MASK;
+            if (docIDs[slot] == docID && counts[slot] >= dim) {
+              // TODO: single-valued dims will always be true
+              // below; we could somehow specialize
+              if (missingDims[slot] >= dim) {
+                //if (DEBUG) {
+                //  System.out.println("    set docID=" + docID + " count=" + (dim+2));
+                //}
+                missingDims[slot] = dim+1;
+                counts[slot] = dim+2;
+              } else {
+                //if (DEBUG) {
+                //  System.out.println("    set docID=" + docID + " missing count=" + (dim+1));
+                //}
+                counts[slot] = dim+1;
+              }
             }
-          }
 
-          // TODO: sometimes use advance?
-          docID = disi.nextDoc();
+            // TODO: sometimes use advance?
+            docID = disi.nextDoc();
+          }
         }
       }
 
@@ -357,7 +406,7 @@ class DrillSidewaysScorer extends Scorer
         if (counts[slot] == 1+numDims) {
           collectHit(collector, sidewaysCollectors);
         } else if (counts[slot] == numDims) {
-          collectNearMiss(sidewaysCollectors, missingDims[slot]);
+          collectNearMiss(sidewaysCollectors[missingDims[slot]]);
         }
       }
 
@@ -432,58 +481,61 @@ class DrillSidewaysScorer extends Scorer
       //  System.out.println("  dim=0 [" + dims[0].dim + "]");
       //}
       DocIdSetIterator disi = disis[0];
-      docID = disi.docID();
-      //if (DEBUG) {
-      //  System.out.println("    start docID=" + docID);
-      //}
-      while (docID < nextChunkStart) {
-        int slot = docID & MASK;
-        if (docIDs[slot] == docID) {
-          //if (DEBUG) {
-          //  System.out.println("      set docID=" + docID + " count=2");
-          //}
-          missingDims[slot] = 1;
-          counts[slot] = 2;
+      if (disi != null) {
+        docID = disi.docID();
+        //if (DEBUG) {
+        //  System.out.println("    start docID=" + docID);
+        //}
+        while (docID < nextChunkStart) {
+          int slot = docID & MASK;
+          if (docIDs[slot] == docID) {
+            //if (DEBUG) {
+            //  System.out.println("      set docID=" + docID + " count=2");
+            //}
+            missingDims[slot] = 1;
+            counts[slot] = 2;
+          }
+          docID = disi.nextDoc();
         }
-        docID = disi.nextDoc();
       }
 
       for (int dim=1;dim<numDims;dim++) {
         //if (DEBUG) {
         //  System.out.println("  dim=" + dim + " [" + dims[dim].dim + "]");
         //}
+
         disi = disis[dim];
-        docID = disi.docID();
-        //if (DEBUG) {
-        //  System.out.println("    start docID=" + docID);
-        //}
-        while (docID < nextChunkStart) {
-          int slot = docID & MASK;
-          if (docIDs[slot] == docID && counts[slot] >= dim) {
-            // This doc is still in the running...
-            // TODO: single-valued dims will always be true
-            // below; we could somehow specialize
-            if (missingDims[slot] >= dim) {
-              //if (DEBUG) {
-              //  System.out.println("      set docID=" + docID + " count=" + (dim+2));
-              //}
-              missingDims[slot] = dim+1;
-              counts[slot] = dim+2;
-            } else {
-              //if (DEBUG) {
-              //  System.out.println("      set docID=" + docID + " missing count=" + (dim+1));
-              //}
-              counts[slot] = dim+1;
+        if (disi != null) {
+          docID = disi.docID();
+          //if (DEBUG) {
+          //  System.out.println("    start docID=" + docID);
+          //}
+          while (docID < nextChunkStart) {
+            int slot = docID & MASK;
+            if (docIDs[slot] == docID && counts[slot] >= dim) {
+              // This doc is still in the running...
+              // TODO: single-valued dims will always be true
+              // below; we could somehow specialize
+              if (missingDims[slot] >= dim) {
+                //if (DEBUG) {
+                //  System.out.println("      set docID=" + docID + " count=" + (dim+2));
+                //}
+                missingDims[slot] = dim+1;
+                counts[slot] = dim+2;
+              } else {
+                //if (DEBUG) {
+                //  System.out.println("      set docID=" + docID + " missing count=" + (dim+1));
+                //}
+                counts[slot] = dim+1;
+              }
             }
+            docID = disi.nextDoc();
           }
-          docID = disi.nextDoc();
         }
       }
 
       // Collect:
-      //if (DEBUG) {
-      //  System.out.println("  now collect: " + filledCount + " hits");
-      //}
+      //System.out.println("  now collect: " + filledCount + " hits");
       for (int i=0;i<filledCount;i++) {
         // NOTE: This is actually in-order collection,
         // because we only accept docs originally returned by
@@ -500,7 +552,7 @@ class DrillSidewaysScorer extends Scorer
           collectHit(collector, sidewaysCollectors);
         } else if (counts[slot] == numDims) {
           //System.out.println("    sw");
-          collectNearMiss(sidewaysCollectors, missingDims[slot]);
+          collectNearMiss(sidewaysCollectors[missingDims[slot]]);
         }
       }
 
@@ -532,11 +584,34 @@ class DrillSidewaysScorer extends Scorer
     }
   }
 
-  private void collectNearMiss(Collector[] sidewaysCollectors, int dim) throws IOException {
+  private void collectHit(Collector collector, Collector[] sidewaysCollectors, Collector[] sidewaysCollectors2) throws IOException {
+    //if (DEBUG) {
+    //  System.out.println("      hit");
+    //}
+
+    collector.collect(collectDocID);
+    if (drillDownCollector != null) {
+      drillDownCollector.collect(collectDocID);
+    }
+
+    // TODO: we could "fix" faceting of the sideways counts
+    // to do this "union" (of the drill down hits) in the
+    // end instead:
+
+    // Tally sideways counts:
+    for (int i=0;i<sidewaysCollectors.length;i++) {
+      sidewaysCollectors[i].collect(collectDocID);
+    }
+    for (int i=0;i<sidewaysCollectors2.length;i++) {
+      sidewaysCollectors2[i].collect(collectDocID);
+    }
+  }
+
+  private void collectNearMiss(Collector sidewaysCollector) throws IOException {
     //if (DEBUG) {
     //  System.out.println("      missingDim=" + dim);
     //}
-    sidewaysCollectors[dim].collect(collectDocID);
+    sidewaysCollector.collect(collectDocID);
   }
 
   @Override
@@ -575,14 +650,24 @@ class DrillSidewaysScorer extends Scorer
   }
 
   static class DocsAndCost implements Comparable<DocsAndCost> {
-    // Docs matching this dim's filter:
+    // Iterator for docs matching this dim's filter, or ...
     DocIdSetIterator disi;
+    // Random access bits:
+    Bits bits;
     Collector sidewaysCollector;
     String dim;
 
     @Override
     public int compareTo(DocsAndCost other) {
-      if (disi.cost() < other.disi.cost()) {
+      if (disi == null) {
+        if (other.disi == null) {
+          return 0;
+        } else {
+          return 1;
+        }
+      } else if (other.disi == null) {
+        return -1;
+      } else if (disi.cost() < other.disi.cost()) {
         return -1;
       } else if (disi.cost() > other.disi.cost()) {
         return 1;

Modified: lucene/dev/branches/lucene5376/lucene/facet/src/java/org/apache/lucene/facet/range/DoubleRange.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/lucene5376/lucene/facet/src/java/org/apache/lucene/facet/range/DoubleRange.java?rev=1562127&r1=1562126&r2=1562127&view=diff
==============================================================================
--- lucene/dev/branches/lucene5376/lucene/facet/src/java/org/apache/lucene/facet/range/DoubleRange.java (original)
+++ lucene/dev/branches/lucene5376/lucene/facet/src/java/org/apache/lucene/facet/range/DoubleRange.java Tue Jan 28 17:07:19 2014
@@ -26,7 +26,6 @@ import org.apache.lucene.queries.functio
 import org.apache.lucene.search.DocIdSet;
 import org.apache.lucene.search.DocIdSetIterator;
 import org.apache.lucene.search.Filter;
-import org.apache.lucene.search.NumericRangeFilter; // javadocs
 import org.apache.lucene.util.Bits;
 import org.apache.lucene.util.NumericUtils;
 
@@ -102,6 +101,12 @@ public final class DoubleRange extends R
   @Override
   public Filter getFilter(final ValueSource valueSource) {
     return new Filter() {
+
+      @Override
+      public String toString() {
+        return "Filter(" + DoubleRange.this.toString();
+      }
+
       @Override
       public DocIdSet getDocIdSet(AtomicReaderContext context, final Bits acceptDocs) throws IOException {
 
@@ -117,51 +122,27 @@ public final class DoubleRange extends R
         return new DocIdSet() {
 
           @Override
-          public DocIdSetIterator iterator() {
-            return new DocIdSetIterator() {
-              int doc = -1;
-
+          public Bits bits() {
+            return new Bits() {
               @Override
-              public int nextDoc() throws IOException {
-                assert doc != NO_MORE_DOCS;
-                while (true) {
-                  doc++;
-                  if (doc == maxDoc) {
-                    return doc = NO_MORE_DOCS;
-                  }
-                  if (acceptDocs != null && acceptDocs.get(doc) == false) {
-                    continue;
-                  }
-                  double v = values.doubleVal(doc);
-                  if (accept(v)) {
-                    return doc;
-                  }
+              public boolean get(int docID) {
+                if (acceptDocs != null && acceptDocs.get(docID) == false) {
+                  return false;
                 }
+                return accept(values.doubleVal(docID));
               }
 
               @Override
-              public int advance(int target) throws IOException {
-                if (target == NO_MORE_DOCS) {
-                  doc = target;
-                  return doc;
-                }
-                doc = target-1;
-                return nextDoc();
-              }
-
-              @Override
-              public int docID() {
-                return doc;
-              }
-
-              @Override
-              public long cost() {
-                // Since we do a linear scan over all
-                // documents, our cost is O(maxDoc):
+              public int length() {
                 return maxDoc;
               }
             };
           }
+
+          @Override
+          public DocIdSetIterator iterator() {
+            return new SlowBitsDocIdSetIterator(bits(), maxDoc, acceptDocs);
+          }
         };
       }
     };

Modified: lucene/dev/branches/lucene5376/lucene/facet/src/java/org/apache/lucene/facet/range/LongRange.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/lucene5376/lucene/facet/src/java/org/apache/lucene/facet/range/LongRange.java?rev=1562127&r1=1562126&r2=1562127&view=diff
==============================================================================
--- lucene/dev/branches/lucene5376/lucene/facet/src/java/org/apache/lucene/facet/range/LongRange.java (original)
+++ lucene/dev/branches/lucene5376/lucene/facet/src/java/org/apache/lucene/facet/range/LongRange.java Tue Jan 28 17:07:19 2014
@@ -26,7 +26,6 @@ import org.apache.lucene.queries.functio
 import org.apache.lucene.search.DocIdSet;
 import org.apache.lucene.search.DocIdSetIterator;
 import org.apache.lucene.search.Filter;
-import org.apache.lucene.search.NumericRangeFilter; // javadocs
 import org.apache.lucene.util.Bits;
 
 /** Represents a range over long values. */
@@ -94,6 +93,12 @@ public final class LongRange extends Ran
   @Override
   public Filter getFilter(final ValueSource valueSource) {
     return new Filter() {
+
+      @Override
+      public String toString() {
+        return "Filter(" + LongRange.this.toString();
+      }
+
       @Override
       public DocIdSet getDocIdSet(AtomicReaderContext context, final Bits acceptDocs) throws IOException {
 
@@ -109,51 +114,27 @@ public final class LongRange extends Ran
         return new DocIdSet() {
 
           @Override
-          public DocIdSetIterator iterator() {
-            return new DocIdSetIterator() {
-              int doc = -1;
-
+          public Bits bits() {
+            return new Bits() {
               @Override
-              public int nextDoc() throws IOException {
-                assert doc != NO_MORE_DOCS;
-                while (true) {
-                  doc++;
-                  if (doc == maxDoc) {
-                    return doc = NO_MORE_DOCS;
-                  }
-                  if (acceptDocs != null && acceptDocs.get(doc) == false) {
-                    continue;
-                  }
-                  long v = values.longVal(doc);
-                  if (accept(v)) {
-                    return doc;
-                  }
+              public boolean get(int docID) {
+                if (acceptDocs != null && acceptDocs.get(docID) == false) {
+                  return false;
                 }
+                return accept(values.longVal(docID));
               }
 
               @Override
-              public int advance(int target) throws IOException {
-                if (target == NO_MORE_DOCS) {
-                  doc = target;
-                  return doc;
-                }
-                doc = target-1;
-                return nextDoc();
-              }
-
-              @Override
-              public int docID() {
-                return doc;
-              }
-
-              @Override
-              public long cost() {
-                // Since we do a linear scan over all
-                // documents, our cost is O(maxDoc):
+              public int length() {
                 return maxDoc;
               }
             };
           }
+
+          @Override
+          public DocIdSetIterator iterator() {
+            return new SlowBitsDocIdSetIterator(bits(), maxDoc, acceptDocs);
+          }
         };
       }
     };

Modified: lucene/dev/branches/lucene5376/lucene/facet/src/java/org/apache/lucene/facet/range/Range.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/lucene5376/lucene/facet/src/java/org/apache/lucene/facet/range/Range.java?rev=1562127&r1=1562126&r2=1562127&view=diff
==============================================================================
--- lucene/dev/branches/lucene5376/lucene/facet/src/java/org/apache/lucene/facet/range/Range.java (original)
+++ lucene/dev/branches/lucene5376/lucene/facet/src/java/org/apache/lucene/facet/range/Range.java Tue Jan 28 17:07:19 2014
@@ -1,8 +1,5 @@
 package org.apache.lucene.facet.range;
 
-import org.apache.lucene.queries.function.ValueSource;
-import org.apache.lucene.search.Filter;
-
 /*
  * Licensed to the Apache Software Foundation (ASF) under one or more
  * contributor license agreements.  See the NOTICE file distributed with
@@ -20,6 +17,10 @@ import org.apache.lucene.search.Filter;
  * limitations under the License.
  */
 
+import org.apache.lucene.queries.function.ValueSource;
+import org.apache.lucene.search.Filter;
+import org.apache.lucene.search.FilteredQuery; // javadocs
+
 /** Base class for a single labeled range.
  *
  *  @lucene.experimental */
@@ -37,11 +38,13 @@ public abstract class Range {
   }
 
   /** Returns a new {@link Filter} accepting only documents
-   *  in this range.  Note that this filter is not
-   *  efficient: it's a linear scan of all docs, testing
-   *  each value.  If the {@link ValueSource} is static,
-   *  e.g. an indexed numeric field, then it's more
-   *  efficient to use {@link NumericRangeFilter}. */
+   *  in this range.  This filter is not general-purpose;
+   *  you should either use it with {@link DrillSideways} by
+   *  adding it to {@link DrillDownQuery#add}, or pass it to
+   *  {@link FilteredQuery} using its {@link
+   *  FilteredQuery#QUERY_FIRST_FILTER_STRATEGY}.  If the
+   *  {@link ValueSource} is static, e.g. an indexed numeric
+   *  field, then it may be more efficient to use {@link NumericRangeFilter}. */
   public abstract Filter getFilter(final ValueSource valueSource);
 
   /** Invoke this for a useless range. */

Added: lucene/dev/branches/lucene5376/lucene/facet/src/java/org/apache/lucene/facet/range/SlowBitsDocIdSetIterator.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/lucene5376/lucene/facet/src/java/org/apache/lucene/facet/range/SlowBitsDocIdSetIterator.java?rev=1562127&view=auto
==============================================================================
--- lucene/dev/branches/lucene5376/lucene/facet/src/java/org/apache/lucene/facet/range/SlowBitsDocIdSetIterator.java (added)
+++ lucene/dev/branches/lucene5376/lucene/facet/src/java/org/apache/lucene/facet/range/SlowBitsDocIdSetIterator.java Tue Jan 28 17:07:19 2014
@@ -0,0 +1,75 @@
+package org.apache.lucene.facet.range;
+
+/*
+ * 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.
+ */
+
+import java.io.IOException;
+
+import org.apache.lucene.search.DocIdSetIterator;
+import org.apache.lucene.util.Bits;
+
+/** Wraps a {@link Bits} to make a {@link DocIdSetIterator};
+ *  this is typically a slow thing to do and should only be
+ *  used as a last resort! */
+class SlowBitsDocIdSetIterator extends DocIdSetIterator {
+  private final Bits bits;
+  private final long cost;
+  private final int maxDoc;
+  private final Bits acceptDocs;
+  private int doc = -1;
+
+  public SlowBitsDocIdSetIterator(Bits bits, long cost, Bits acceptDocs) {
+    this.bits = bits;
+    this.cost = cost;
+    this.maxDoc = bits.length();
+    this.acceptDocs = acceptDocs;
+  }
+
+  @Override
+  public int advance(int target) throws IOException {
+    if (target == NO_MORE_DOCS) {
+      doc = target;
+      return doc;
+    }
+    doc = target-1;
+    return nextDoc();
+  }
+
+  @Override
+  public int nextDoc() throws IOException {
+    assert doc != NO_MORE_DOCS;
+    while (true) {
+      doc++;
+      if (doc == maxDoc) {
+        return doc = NO_MORE_DOCS;
+      }
+      if ((acceptDocs == null || acceptDocs.get(doc)) && bits.get(doc)) {
+        return doc;
+      }
+    }
+  }
+
+  @Override
+  public int docID() {
+    return doc;
+  }
+
+  @Override
+  public long cost() {
+    return cost;
+  }
+}
\ No newline at end of file

Modified: lucene/dev/branches/lucene5376/lucene/facet/src/test/org/apache/lucene/facet/range/TestRangeFacetCounts.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/lucene5376/lucene/facet/src/test/org/apache/lucene/facet/range/TestRangeFacetCounts.java?rev=1562127&r1=1562126&r2=1562127&view=diff
==============================================================================
--- lucene/dev/branches/lucene5376/lucene/facet/src/test/org/apache/lucene/facet/range/TestRangeFacetCounts.java (original)
+++ lucene/dev/branches/lucene5376/lucene/facet/src/test/org/apache/lucene/facet/range/TestRangeFacetCounts.java Tue Jan 28 17:07:19 2014
@@ -51,7 +51,6 @@ import org.apache.lucene.queries.functio
 import org.apache.lucene.queries.function.ValueSource;
 import org.apache.lucene.queries.function.docvalues.DoubleDocValues;
 import org.apache.lucene.queries.function.valuesource.FloatFieldSource;
-import org.apache.lucene.search.ConstantScoreQuery;
 import org.apache.lucene.search.IndexSearcher;
 import org.apache.lucene.search.MatchAllDocsQuery;
 import org.apache.lucene.search.NumericRangeQuery;
@@ -229,6 +228,10 @@ public class TestRangeFacetCounts extend
 
     IndexSearcher s = newSearcher(r);
 
+    if (VERBOSE) {
+      System.out.println("TEST: searcher=" + s);
+    }
+
     DrillSideways ds = new DrillSideways(s, config, tr) {
 
         @Override
@@ -765,16 +768,13 @@ public class TestRangeFacetCounts extend
     
     Document doc = new Document();
     writer.addDocument(doc);
-    
-    doc = new Document();
     writer.addDocument(doc);
-    
-    doc = new Document();
     writer.addDocument(doc);
 
+    // Test wants 3 docs in one segment:
     writer.forceMerge(1);
 
-    ValueSource vs = new ValueSource() {
+    final ValueSource vs = new ValueSource() {
         @SuppressWarnings("rawtypes")
         @Override
         public FunctionValues getValues(Map ignored, AtomicReaderContext ignored2) {
@@ -801,6 +801,8 @@ public class TestRangeFacetCounts extend
           throw new UnsupportedOperationException();
         }
       };
+
+    FacetsConfig config = new FacetsConfig();
     
     FacetsCollector fc = new FacetsCollector();
 
@@ -808,18 +810,44 @@ public class TestRangeFacetCounts extend
     IndexSearcher s = newSearcher(r);
     s.search(new MatchAllDocsQuery(), fc);
 
-    Facets facets = new DoubleRangeFacetCounts("field", vs, fc,
+    final DoubleRange[] ranges = new DoubleRange[] {
         new DoubleRange("< 1", 0.0, true, 1.0, false),
         new DoubleRange("< 2", 0.0, true, 2.0, false),
         new DoubleRange("< 5", 0.0, true, 5.0, false),
         new DoubleRange("< 10", 0.0, true, 10.0, false),
         new DoubleRange("< 20", 0.0, true, 20.0, false),
-        new DoubleRange("< 50", 0.0, true, 50.0, false));
+        new DoubleRange("< 50", 0.0, true, 50.0, false)};
+
+    Facets facets = new DoubleRangeFacetCounts("field", vs, fc, ranges);
 
     assertEquals("dim=field path=[] value=3 childCount=6\n  < 1 (0)\n  < 2 (1)\n  < 5 (3)\n  < 10 (3)\n  < 20 (3)\n  < 50 (3)\n", facets.getTopChildren(10, "field").toString());
 
-    // Test drill-down:
-    assertEquals(1, s.search(new ConstantScoreQuery(new DoubleRange("< 2", 0.0, true, 2.0, false).getFilter(vs)), 10).totalHits);
+    DrillDownQuery ddq = new DrillDownQuery(config);
+    ddq.add("field", ranges[1].getFilter(vs));
+
+    // Test simple drill-down:
+    assertEquals(1, s.search(ddq, 10).totalHits);
+
+    // Test drill-sideways after drill-down
+    DrillSideways ds = new DrillSideways(s, config, (TaxonomyReader) null) {
+
+        @Override
+        protected Facets buildFacetsResult(FacetsCollector drillDowns, FacetsCollector[] drillSideways, String[] drillSidewaysDims) throws IOException {        
+          assert drillSideways.length == 1;
+          return new DoubleRangeFacetCounts("field", vs, drillSideways[0], ranges);
+        }
+
+        @Override
+        protected boolean scoreSubDocsAtOnce() {
+          return random().nextBoolean();
+        }
+      };
+
+
+    DrillSidewaysResult dsr = ds.search(ddq, 10);
+    assertEquals(1, dsr.hits.totalHits);
+    assertEquals("dim=field path=[] value=3 childCount=6\n  < 1 (0)\n  < 2 (1)\n  < 5 (3)\n  < 10 (3)\n  < 20 (3)\n  < 50 (3)\n",
+                 dsr.facets.getTopChildren(10, "field").toString());
 
     IOUtils.close(r, writer, dir);
   }