You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by rm...@apache.org on 2015/03/01 14:48:10 UTC

svn commit: r1663100 - in /lucene/dev/branches/branch_5x: ./ lucene/ lucene/core/ lucene/core/src/java/org/apache/lucene/search/ lucene/join/ lucene/join/src/test/org/apache/lucene/search/join/ lucene/queries/ lucene/queries/src/java/org/apache/lucene/...

Author: rmuir
Date: Sun Mar  1 13:48:09 2015
New Revision: 1663100

URL: http://svn.apache.org/r1663100
Log:
LUCENE-6310: FilterScorer supports two-phase always, adds two-phase support to some long-tail scorers

Modified:
    lucene/dev/branches/branch_5x/   (props changed)
    lucene/dev/branches/branch_5x/lucene/   (props changed)
    lucene/dev/branches/branch_5x/lucene/core/   (props changed)
    lucene/dev/branches/branch_5x/lucene/core/src/java/org/apache/lucene/search/BooleanTopLevelScorers.java
    lucene/dev/branches/branch_5x/lucene/core/src/java/org/apache/lucene/search/ConstantScoreQuery.java
    lucene/dev/branches/branch_5x/lucene/core/src/java/org/apache/lucene/search/FilterScorer.java
    lucene/dev/branches/branch_5x/lucene/core/src/java/org/apache/lucene/search/FilteredQuery.java
    lucene/dev/branches/branch_5x/lucene/core/src/java/org/apache/lucene/search/ReqExclScorer.java
    lucene/dev/branches/branch_5x/lucene/join/   (props changed)
    lucene/dev/branches/branch_5x/lucene/join/src/test/org/apache/lucene/search/join/TestBlockJoinValidation.java
    lucene/dev/branches/branch_5x/lucene/queries/   (props changed)
    lucene/dev/branches/branch_5x/lucene/queries/src/java/org/apache/lucene/queries/CustomScoreQuery.java
    lucene/dev/branches/branch_5x/lucene/sandbox/   (props changed)
    lucene/dev/branches/branch_5x/lucene/sandbox/src/java/org/apache/lucene/search/TermAutomatonScorer.java
    lucene/dev/branches/branch_5x/lucene/test-framework/   (props changed)
    lucene/dev/branches/branch_5x/lucene/test-framework/src/java/org/apache/lucene/search/AssertingScorer.java
    lucene/dev/branches/branch_5x/lucene/test-framework/src/java/org/apache/lucene/search/SearchEquivalenceTestBase.java

Modified: lucene/dev/branches/branch_5x/lucene/core/src/java/org/apache/lucene/search/BooleanTopLevelScorers.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_5x/lucene/core/src/java/org/apache/lucene/search/BooleanTopLevelScorers.java?rev=1663100&r1=1663099&r2=1663100&view=diff
==============================================================================
--- lucene/dev/branches/branch_5x/lucene/core/src/java/org/apache/lucene/search/BooleanTopLevelScorers.java (original)
+++ lucene/dev/branches/branch_5x/lucene/core/src/java/org/apache/lucene/search/BooleanTopLevelScorers.java Sun Mar  1 13:48:09 2015
@@ -39,11 +39,6 @@ class BooleanTopLevelScorers {
     }
 
     @Override
-    public TwoPhaseIterator asTwoPhaseIterator() {
-      return in.asTwoPhaseIterator();
-    }
-
-    @Override
     public float score() throws IOException {
       return in.score() * boost;
     }

Modified: lucene/dev/branches/branch_5x/lucene/core/src/java/org/apache/lucene/search/ConstantScoreQuery.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_5x/lucene/core/src/java/org/apache/lucene/search/ConstantScoreQuery.java?rev=1663100&r1=1663099&r2=1663100&view=diff
==============================================================================
--- lucene/dev/branches/branch_5x/lucene/core/src/java/org/apache/lucene/search/ConstantScoreQuery.java (original)
+++ lucene/dev/branches/branch_5x/lucene/core/src/java/org/apache/lucene/search/ConstantScoreQuery.java Sun Mar  1 13:48:09 2015
@@ -183,11 +183,6 @@ public class ConstantScoreQuery extends
     }
 
     @Override
-    public TwoPhaseIterator asTwoPhaseIterator() {
-      return in.asTwoPhaseIterator();
-    }
-
-    @Override
     public int freq() throws IOException {
       return 1;
     }

Modified: lucene/dev/branches/branch_5x/lucene/core/src/java/org/apache/lucene/search/FilterScorer.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_5x/lucene/core/src/java/org/apache/lucene/search/FilterScorer.java?rev=1663100&r1=1663099&r2=1663100&view=diff
==============================================================================
--- lucene/dev/branches/branch_5x/lucene/core/src/java/org/apache/lucene/search/FilterScorer.java (original)
+++ lucene/dev/branches/branch_5x/lucene/core/src/java/org/apache/lucene/search/FilterScorer.java Sun Mar  1 13:48:09 2015
@@ -62,17 +62,17 @@ public abstract class FilterScorer exten
   }
 
   @Override
-  public int docID() {
+  public final int docID() {
     return in.docID();
   }
 
   @Override
-  public int nextDoc() throws IOException {
+  public final int nextDoc() throws IOException {
     return in.nextDoc();
   }
 
   @Override
-  public int advance(int target) throws IOException {
+  public final int advance(int target) throws IOException {
     return in.advance(target);
   }
 
@@ -80,5 +80,9 @@ public abstract class FilterScorer exten
   public long cost() {
     return in.cost();
   }
-
+  
+  @Override
+  public final TwoPhaseIterator asTwoPhaseIterator() {
+    return in.asTwoPhaseIterator();
+  }
 }

Modified: lucene/dev/branches/branch_5x/lucene/core/src/java/org/apache/lucene/search/FilteredQuery.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_5x/lucene/core/src/java/org/apache/lucene/search/FilteredQuery.java?rev=1663100&r1=1663099&r2=1663100&view=diff
==============================================================================
--- lucene/dev/branches/branch_5x/lucene/core/src/java/org/apache/lucene/search/FilteredQuery.java (original)
+++ lucene/dev/branches/branch_5x/lucene/core/src/java/org/apache/lucene/search/FilteredQuery.java Sun Mar  1 13:48:09 2015
@@ -18,6 +18,7 @@ package org.apache.lucene.search;
  */
 
 import java.io.IOException;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Set;
@@ -154,13 +155,12 @@ public class FilteredQuery extends Query
    * than document scoring or if the filter has a linear running time to compute
    * the next matching doc like exact geo distances.
    */
-  private static final class QueryFirstScorer extends FilterScorer {
+  private static final class QueryFirstScorer extends Scorer {
     private final Scorer scorer;
-    private int scorerDoc = -1;
     private final Bits filterBits;
 
     protected QueryFirstScorer(Weight weight, Bits filterBits, Scorer other) {
-      super(other, weight);
+      super(weight);
       this.scorer = other;
       this.filterBits = filterBits;
     }
@@ -170,8 +170,8 @@ public class FilteredQuery extends Query
       int doc;
       for(;;) {
         doc = scorer.nextDoc();
-        if (doc == Scorer.NO_MORE_DOCS || filterBits.get(doc)) {
-          return scorerDoc = doc;
+        if (doc == DocIdSetIterator.NO_MORE_DOCS || filterBits.get(doc)) {
+          return doc;
         }
       } 
     }
@@ -179,15 +179,31 @@ 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();
+      if (doc != DocIdSetIterator.NO_MORE_DOCS && !filterBits.get(doc)) {
+        return nextDoc();
       } else {
-        return scorerDoc = doc;
+        return doc;
       }
     }
+    
     @Override
     public int docID() {
-      return scorerDoc;
+      return scorer.docID();
+    }
+
+    @Override
+    public float score() throws IOException {
+      return scorer.score();
+    }
+
+    @Override
+    public int freq() throws IOException {
+      return scorer.freq();
+    }
+
+    @Override
+    public long cost() {
+      return scorer.cost();
     }
 
     @Override
@@ -195,6 +211,37 @@ public class FilteredQuery extends Query
       return Collections.singleton(new ChildScorer(scorer, "FILTERED"));
     }
 
+    @Override
+    public TwoPhaseIterator asTwoPhaseIterator() {    
+      final TwoPhaseIterator inner = scorer.asTwoPhaseIterator();
+      if (inner != null) {
+        // we are like a simplified conjunction here, handle the nested case:
+        return new TwoPhaseIterator() {
+          @Override
+          public DocIdSetIterator approximation() {
+            return inner.approximation();
+          }
+          @Override
+          public boolean matches() throws IOException {
+            // check the approximation matches first, then check bits last.
+            return inner.matches() && filterBits.get(scorer.docID());
+          }
+        };
+      } else {
+        // scorer doesnt have an approximation, just use it, to force bits applied last.
+        return new TwoPhaseIterator() {
+          @Override
+          public DocIdSetIterator approximation() {
+            return scorer;
+          }
+          
+          @Override
+          public boolean matches() throws IOException {
+            return filterBits.get(scorer.docID());
+          }
+        };
+      }
+    }
   }
 
   private static class QueryFirstBulkScorer extends BulkScorer {
@@ -242,53 +289,29 @@ public class FilteredQuery extends Query
    * jumping past the target document. When both land on the same document, it's
    * collected.
    */
-  private static final class LeapFrogScorer extends FilterScorer {
-    private final DocIdSetIterator secondary;
-    private final DocIdSetIterator primary;
+  private static final class LeapFrogScorer extends Scorer {
+    private final ConjunctionDISI conjunction;
     private final Scorer scorer;
-    private int primaryDoc = -1;
-    private int secondaryDoc = -1;
 
     protected LeapFrogScorer(Weight weight, DocIdSetIterator primary, DocIdSetIterator secondary, Scorer scorer) {
-      super(scorer, weight);
-      this.primary = primary;
-      this.secondary = secondary;
+      super(weight);
+      conjunction = ConjunctionDISI.intersect(Arrays.asList(primary, secondary));
       this.scorer = scorer;
     }
 
-    private final int advanceToNextCommonDoc() throws IOException {
-      for (;;) {
-        if (secondaryDoc < primaryDoc) {
-          secondaryDoc = secondary.advance(primaryDoc);
-        } else if (secondaryDoc == primaryDoc) {
-          return primaryDoc;
-        } else {
-          primaryDoc = primary.advance(secondaryDoc);
-        }
-      }
-    }
-
     @Override
-    public final int nextDoc() throws IOException {
-      primaryDoc = primaryNext();
-      return advanceToNextCommonDoc();
-    }
-    
-    protected int primaryNext() throws IOException {
-      return primary.nextDoc();
+    public int nextDoc() throws IOException {
+      return conjunction.nextDoc();
     }
     
     @Override
     public final int advance(int target) throws IOException {
-      if (target > primaryDoc) {
-        primaryDoc = primary.advance(target);
-      }
-      return advanceToNextCommonDoc();
+      return conjunction.advance(target);
     }
 
     @Override
     public final int docID() {
-      return secondaryDoc;
+      return conjunction.docID();
     }
 
     @Override
@@ -298,7 +321,22 @@ public class FilteredQuery extends Query
 
     @Override
     public long cost() {
-      return Math.min(primary.cost(), secondary.cost());
+      return conjunction.cost();
+    }
+
+    @Override
+    public float score() throws IOException {
+      return scorer.score();
+    }
+
+    @Override
+    public int freq() throws IOException {
+      return scorer.freq();
+    }
+
+    @Override
+    public TwoPhaseIterator asTwoPhaseIterator() {
+      return conjunction.asTwoPhaseIterator();
     }
   }
   

Modified: lucene/dev/branches/branch_5x/lucene/core/src/java/org/apache/lucene/search/ReqExclScorer.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_5x/lucene/core/src/java/org/apache/lucene/search/ReqExclScorer.java?rev=1663100&r1=1663099&r2=1663100&view=diff
==============================================================================
--- lucene/dev/branches/branch_5x/lucene/core/src/java/org/apache/lucene/search/ReqExclScorer.java (original)
+++ lucene/dev/branches/branch_5x/lucene/core/src/java/org/apache/lucene/search/ReqExclScorer.java Sun Mar  1 13:48:09 2015
@@ -27,7 +27,7 @@ import java.util.Collections;
  * This <code>Scorer</code> implements {@link Scorer#advance(int)},
  * and it uses the advance() on the given scorers.
  */
-class ReqExclScorer extends FilterScorer {
+class ReqExclScorer extends Scorer {
 
   private final Scorer reqScorer;
   // approximations of the scorers, or the scorers themselves if they don't support approximations
@@ -42,7 +42,7 @@ class ReqExclScorer extends FilterScorer
    * @param exclScorer indicates exclusion.
    */
   public ReqExclScorer(Scorer reqScorer, Scorer exclScorer) {
-    super(reqScorer);
+    super(reqScorer.weight);
     this.reqScorer = reqScorer;
     reqTwoPhaseIterator = reqScorer.asTwoPhaseIterator();
     if (reqTwoPhaseIterator == null) {
@@ -106,10 +106,16 @@ class ReqExclScorer extends FilterScorer
     return reqScorer.docID();
   }
 
-  /** Returns the score of the current document matching the query.
-   * Initially invalid, until {@link #nextDoc()} is called the first time.
-   * @return The score of the required scorer.
-   */
+  @Override
+  public int freq() throws IOException {
+    return reqScorer.freq();
+  }
+
+  @Override
+  public long cost() {
+    return reqScorer.cost();
+  }
+
   @Override
   public float score() throws IOException {
     return reqScorer.score(); // reqScorer may be null when next() or skipTo() already return false

Modified: lucene/dev/branches/branch_5x/lucene/join/src/test/org/apache/lucene/search/join/TestBlockJoinValidation.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_5x/lucene/join/src/test/org/apache/lucene/search/join/TestBlockJoinValidation.java?rev=1663100&r1=1663099&r2=1663100&view=diff
==============================================================================
--- lucene/dev/branches/branch_5x/lucene/join/src/test/org/apache/lucene/search/join/TestBlockJoinValidation.java (original)
+++ lucene/dev/branches/branch_5x/lucene/join/src/test/org/apache/lucene/search/join/TestBlockJoinValidation.java Sun Mar  1 13:48:09 2015
@@ -17,7 +17,6 @@ package org.apache.lucene.search.join;
  * limitations under the License.
  */
 
-import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -28,20 +27,15 @@ import org.apache.lucene.index.Directory
 import org.apache.lucene.index.IndexReader;
 import org.apache.lucene.index.IndexWriter;
 import org.apache.lucene.index.IndexWriterConfig;
-import org.apache.lucene.index.LeafReaderContext;
 import org.apache.lucene.index.Term;
 import org.apache.lucene.search.BooleanClause;
 import org.apache.lucene.search.BooleanQuery;
-import org.apache.lucene.search.DocIdSet;
-import org.apache.lucene.search.Filter;
-import org.apache.lucene.search.FilteredQuery;
 import org.apache.lucene.search.IndexSearcher;
 import org.apache.lucene.search.Query;
 import org.apache.lucene.search.QueryWrapperFilter;
 import org.apache.lucene.search.TermQuery;
 import org.apache.lucene.search.WildcardQuery;
 import org.apache.lucene.store.Directory;
-import org.apache.lucene.util.Bits;
 import org.apache.lucene.util.LuceneTestCase;
 
 public class TestBlockJoinValidation extends LuceneTestCase {
@@ -125,41 +119,6 @@ public class TestBlockJoinValidation ext
     }
   }
 
-  // a filter for which other queries don't have special rewrite rules
-  private static class FilterWrapper extends Filter {
-
-    private final Filter in;
-    
-    FilterWrapper(Filter in) {
-      this.in = in;
-    }
-    
-    @Override
-    public DocIdSet getDocIdSet(LeafReaderContext context, Bits acceptDocs) throws IOException {
-      return in.getDocIdSet(context, acceptDocs);
-    }
-
-    @Override
-    public String toString(String field) {
-      return in.toString(field);
-    }
-    
-  }
-
-  public void testValidationForToChildBjqWithChildFilterQuery() throws Exception {
-    Query parentQueryWithRandomChild = createParentQuery();
-
-    ToChildBlockJoinQuery blockJoinQuery = new ToChildBlockJoinQuery(parentQueryWithRandomChild, parentsFilter);
-    Filter childFilter = new FilterWrapper(new QueryWrapperFilter(new TermQuery(new Term("common_field", "1"))));
-
-    try {
-      indexSearcher.search(new FilteredQuery(blockJoinQuery, childFilter), 1);
-      fail("didn't get expected exception");
-    } catch (IllegalStateException expected) {
-      assertTrue(expected.getMessage() != null && expected.getMessage().contains(ToChildBlockJoinQuery.ILLEGAL_ADVANCE_ON_PARENT));
-    }
-  }
-
   public void testAdvanceValidationForToChildBjq() throws Exception {
     int randomChildNumber = getRandomChildNumber(0);
     // we need to make advance method meet wrong document, so random child number

Modified: lucene/dev/branches/branch_5x/lucene/queries/src/java/org/apache/lucene/queries/CustomScoreQuery.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_5x/lucene/queries/src/java/org/apache/lucene/queries/CustomScoreQuery.java?rev=1663100&r1=1663099&r2=1663100&view=diff
==============================================================================
--- lucene/dev/branches/branch_5x/lucene/queries/src/java/org/apache/lucene/queries/CustomScoreQuery.java (original)
+++ lucene/dev/branches/branch_5x/lucene/queries/src/java/org/apache/lucene/queries/CustomScoreQuery.java Sun Mar  1 13:48:09 2015
@@ -30,9 +30,11 @@ import org.apache.lucene.queries.functio
 import org.apache.lucene.queries.function.ValueSource;
 import org.apache.lucene.search.ComplexExplanation;
 import org.apache.lucene.search.Explanation;
+import org.apache.lucene.search.FilterScorer;
 import org.apache.lucene.search.IndexSearcher;
 import org.apache.lucene.search.Query;
 import org.apache.lucene.search.Scorer;
+import org.apache.lucene.search.TwoPhaseIterator;
 import org.apache.lucene.search.Weight;
 import org.apache.lucene.util.Bits;
 import org.apache.lucene.util.ToStringUtils;
@@ -274,45 +276,36 @@ public class CustomScoreQuery extends Qu
   /**
    * A scorer that applies a (callback) function on scores of the subQuery.
    */
-  private class CustomScorer extends Scorer {
+  private class CustomScorer extends FilterScorer {
     private final float qWeight;
     private final Scorer subQueryScorer;
     private final Scorer[] valSrcScorers;
     private final CustomScoreProvider provider;
     private final float[] vScores; // reused in score() to avoid allocating this array for each doc
-
-    // TODO : can we use FilterScorer here instead?
+    private int valSrcDocID = -1; // we lazily advance subscorers.
 
     // constructor
     private CustomScorer(CustomScoreProvider provider, CustomWeight w, float qWeight,
         Scorer subQueryScorer, Scorer[] valSrcScorers) {
-      super(w);
+      super(subQueryScorer, w);
       this.qWeight = qWeight;
       this.subQueryScorer = subQueryScorer;
       this.valSrcScorers = valSrcScorers;
       this.vScores = new float[valSrcScorers.length];
       this.provider = provider;
     }
-
+    
     @Override
-    public int nextDoc() throws IOException {
-      int doc = subQueryScorer.nextDoc();
-      if (doc != NO_MORE_DOCS) {
+    public float score() throws IOException {
+      // lazily advance to current doc.
+      int doc = docID();
+      if (doc > valSrcDocID) {
         for (Scorer valSrcScorer : valSrcScorers) {
           valSrcScorer.advance(doc);
         }
+        valSrcDocID = doc;
       }
-      return doc;
-    }
-
-    @Override
-    public int docID() {
-      return subQueryScorer.docID();
-    }
-    
-    /*(non-Javadoc) @see org.apache.lucene.search.Scorer#score() */
-    @Override
-    public float score() throws IOException {
+      // TODO: this thing technically takes any Query, so what about when subs don't match?
       for (int i = 0; i < valSrcScorers.length; i++) {
         vScores[i] = valSrcScorers[i].score();
       }
@@ -320,30 +313,9 @@ public class CustomScoreQuery extends Qu
     }
 
     @Override
-    public int freq() throws IOException {
-      return subQueryScorer.freq();
-    }
-
-    @Override
     public Collection<ChildScorer> getChildren() {
       return Collections.singleton(new ChildScorer(subQueryScorer, "CUSTOM"));
     }
-
-    @Override
-    public int advance(int target) throws IOException {
-      int doc = subQueryScorer.advance(target);
-      if (doc != NO_MORE_DOCS) {
-        for (Scorer valSrcScorer : valSrcScorers) {
-          valSrcScorer.advance(doc);
-        }
-      }
-      return doc;
-    }
-
-    @Override
-    public long cost() {
-      return subQueryScorer.cost();
-    }
   }
 
   @Override

Modified: lucene/dev/branches/branch_5x/lucene/sandbox/src/java/org/apache/lucene/search/TermAutomatonScorer.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_5x/lucene/sandbox/src/java/org/apache/lucene/search/TermAutomatonScorer.java?rev=1663100&r1=1663099&r2=1663100&view=diff
==============================================================================
--- lucene/dev/branches/branch_5x/lucene/sandbox/src/java/org/apache/lucene/search/TermAutomatonScorer.java (original)
+++ lucene/dev/branches/branch_5x/lucene/sandbox/src/java/org/apache/lucene/search/TermAutomatonScorer.java Sun Mar  1 13:48:09 2015
@@ -30,6 +30,7 @@ import org.apache.lucene.util.RamUsageEs
 import org.apache.lucene.util.automaton.Automaton;
 import org.apache.lucene.util.automaton.RunAutomaton;
 
+// TODO: add two-phase and needsScores support. maybe use conjunctionDISI internally?
 class TermAutomatonScorer extends Scorer {
   private final EnumAndScorer[] subs;
   private final EnumAndScorer[] subsOnDoc;

Modified: lucene/dev/branches/branch_5x/lucene/test-framework/src/java/org/apache/lucene/search/AssertingScorer.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_5x/lucene/test-framework/src/java/org/apache/lucene/search/AssertingScorer.java?rev=1663100&r1=1663099&r2=1663100&view=diff
==============================================================================
--- lucene/dev/branches/branch_5x/lucene/test-framework/src/java/org/apache/lucene/search/AssertingScorer.java (original)
+++ lucene/dev/branches/branch_5x/lucene/test-framework/src/java/org/apache/lucene/search/AssertingScorer.java Sun Mar  1 13:48:09 2015
@@ -27,6 +27,8 @@ import java.util.WeakHashMap;
 
 /** Wraps a Scorer with additional checks */
 public class AssertingScorer extends Scorer {
+  
+  // TODO: add asserts for two-phase intersection
 
   static enum IteratorState { START, ITERATING, FINISHED };
 

Modified: lucene/dev/branches/branch_5x/lucene/test-framework/src/java/org/apache/lucene/search/SearchEquivalenceTestBase.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_5x/lucene/test-framework/src/java/org/apache/lucene/search/SearchEquivalenceTestBase.java?rev=1663100&r1=1663099&r2=1663100&view=diff
==============================================================================
--- lucene/dev/branches/branch_5x/lucene/test-framework/src/java/org/apache/lucene/search/SearchEquivalenceTestBase.java (original)
+++ lucene/dev/branches/branch_5x/lucene/test-framework/src/java/org/apache/lucene/search/SearchEquivalenceTestBase.java Sun Mar  1 13:48:09 2015
@@ -17,6 +17,7 @@ package org.apache.lucene.search;
  * limitations under the License.
  */
 
+import java.io.IOException;
 import java.util.BitSet;
 import java.util.Random;
 
@@ -28,10 +29,13 @@ import org.apache.lucene.document.Field;
 import org.apache.lucene.document.StringField;
 import org.apache.lucene.document.TextField;
 import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.LeafReaderContext;
 import org.apache.lucene.index.RandomIndexWriter;
 import org.apache.lucene.index.Term;
 import org.apache.lucene.search.BooleanClause.Occur;
 import org.apache.lucene.store.Directory;
+import org.apache.lucene.util.BitDocIdSet;
+import org.apache.lucene.util.Bits;
 import org.apache.lucene.util.LuceneTestCase;
 import org.apache.lucene.util.TestUtil;
 import org.apache.lucene.util.automaton.Automata;
@@ -140,7 +144,86 @@ public abstract class SearchEquivalenceT
    * Returns a random filter over the document set
    */
   protected Filter randomFilter() {
-    return new QueryWrapperFilter(TermRangeQuery.newStringRange("field", "a", "" + randomChar(), true, true));
+    final Query query;
+    if (random().nextBoolean()) {
+      query = TermRangeQuery.newStringRange("field", "a", "" + randomChar(), true, true);
+    } else {
+      // use a query with a two-phase approximation
+      PhraseQuery phrase = new PhraseQuery();
+      phrase.add(new Term("field", "" + randomChar()));
+      phrase.add(new Term("field", "" + randomChar()));
+      phrase.setSlop(100);
+      query = phrase;
+    }
+    
+    // now wrap the query as a filter. QWF has its own codepath
+    if (random().nextBoolean()) {
+      return new QueryWrapperFilter(query);
+    } else {
+      return new SlowWrapperFilter(query, random().nextBoolean());
+    }
+  }
+  
+  static class SlowWrapperFilter extends Filter {
+    final Query query;
+    final boolean useBits;
+    
+    SlowWrapperFilter(Query query, boolean useBits) {
+      this.query = query;
+      this.useBits = useBits;
+    }
+    
+    @Override
+    public Query rewrite(IndexReader reader) throws IOException {
+      Query q = query.rewrite(reader);
+      if (q != query) {
+        return new SlowWrapperFilter(q, useBits);
+      } else {
+        return this;
+      }
+    }
+
+    @Override
+    public DocIdSet getDocIdSet(final LeafReaderContext context, final Bits acceptDocs) throws IOException {
+      // get a private context that is used to rewrite, createWeight and score eventually
+      final LeafReaderContext privateContext = context.reader().getContext();
+      final Weight weight = new IndexSearcher(privateContext).createNormalizedWeight(query, false);
+      return new DocIdSet() {
+        @Override
+        public DocIdSetIterator iterator() throws IOException {
+          return weight.scorer(privateContext, acceptDocs);
+        }
+
+        @Override
+        public long ramBytesUsed() {
+          return 0L;
+        }
+
+        @Override
+        public Bits bits() throws IOException {
+          if (useBits) {
+            BitDocIdSet.Builder builder = new BitDocIdSet.Builder(context.reader().maxDoc());
+            DocIdSetIterator disi = iterator();
+            if (disi != null) {
+              builder.or(disi);
+            }
+            BitDocIdSet bitset = builder.build();
+            if (bitset == null) {
+              return new Bits.MatchNoBits(context.reader().maxDoc());
+            } else {
+              return bitset.bits();
+            }
+          } else {
+            return null;
+          }
+        }
+      };
+    }
+
+    @Override
+    public String toString(String field) {
+      return "SlowQWF(" + query + ")";
+    }
   }
 
   /**