You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by jp...@apache.org on 2015/02/25 16:26:05 UTC

svn commit: r1662244 - in /lucene/dev/trunk: lucene/ lucene/core/src/java/org/apache/lucene/search/ lucene/core/src/test/org/apache/lucene/search/ lucene/test-framework/src/java/org/apache/lucene/search/ solr/core/src/java/org/apache/solr/search/

Author: jpountz
Date: Wed Feb 25 15:26:04 2015
New Revision: 1662244

URL: http://svn.apache.org/r1662244
Log:
LUCENE-6289: Replace DocValuesRangeFilter with DocValuesRangeQuery.

Added:
    lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/search/ConstantScoreWeight.java   (with props)
    lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/search/DocValuesTermsQuery.java   (with props)
    lucene/dev/trunk/lucene/core/src/test/org/apache/lucene/search/TestDocValuesTermsQuery.java   (with props)
Removed:
    lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/search/DocValuesTermsFilter.java
Modified:
    lucene/dev/trunk/lucene/CHANGES.txt
    lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/search/DocValuesRangeQuery.java
    lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/search/FieldValueQuery.java
    lucene/dev/trunk/lucene/core/src/test/org/apache/lucene/search/TestComplexExplanations.java
    lucene/dev/trunk/lucene/core/src/test/org/apache/lucene/search/TestFieldCacheTermsFilter.java
    lucene/dev/trunk/lucene/core/src/test/org/apache/lucene/search/TestSimpleExplanations.java
    lucene/dev/trunk/lucene/test-framework/src/java/org/apache/lucene/search/BaseExplanationTestCase.java
    lucene/dev/trunk/solr/core/src/java/org/apache/solr/search/TermsQParserPlugin.java

Modified: lucene/dev/trunk/lucene/CHANGES.txt
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/lucene/CHANGES.txt?rev=1662244&r1=1662243&r2=1662244&view=diff
==============================================================================
--- lucene/dev/trunk/lucene/CHANGES.txt (original)
+++ lucene/dev/trunk/lucene/CHANGES.txt Wed Feb 25 15:26:04 2015
@@ -150,6 +150,9 @@ API Changes
 * LUCENE-6268: Replace FieldValueFilter and DocValuesRangeFilter with equivalent
   queries that support approximations. (Adrien Grand)
 
+* LUCENE-6289: Replace DocValuesRangeFilter with DocValuesRangeQuery which
+  supports approximations. (Adrien Grand)
+
 * LUCENE-6266: Remove unnecessary Directory params from SegmentInfo.toString, 
   SegmentInfos.files/toString, and SegmentCommitInfo.toString. (Robert Muir)
 

Added: lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/search/ConstantScoreWeight.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/search/ConstantScoreWeight.java?rev=1662244&view=auto
==============================================================================
--- lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/search/ConstantScoreWeight.java (added)
+++ lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/search/ConstantScoreWeight.java Wed Feb 25 15:26:04 2015
@@ -0,0 +1,73 @@
+package org.apache.lucene.search;
+
+/*
+ * 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.index.LeafReaderContext;
+import org.apache.lucene.util.Bits;
+
+abstract class ConstantScoreWeight extends Weight {
+
+  private float queryNorm;
+  private float queryWeight;
+
+  protected ConstantScoreWeight(Query query) {
+    super(query);
+  }
+
+  @Override
+  public final float getValueForNormalization() throws IOException {
+    queryWeight = getQuery().getBoost();
+    return queryWeight * queryWeight;
+  }
+
+  @Override
+  public final void normalize(float norm, float topLevelBoost) {
+    queryNorm = norm * topLevelBoost;
+    queryWeight *= queryNorm;
+  }
+
+  @Override
+  public final Explanation explain(LeafReaderContext context, int doc) throws IOException {
+    final Scorer s = scorer(context, context.reader().getLiveDocs());
+    final boolean exists = (s != null && s.advance(doc) == doc);
+
+    final ComplexExplanation result = new ComplexExplanation();
+    if (exists) {
+      result.setDescription(getQuery().toString() + ", product of:");
+      result.setValue(queryWeight);
+      result.setMatch(Boolean.TRUE);
+      result.addDetail(new Explanation(getQuery().getBoost(), "boost"));
+      result.addDetail(new Explanation(queryNorm, "queryNorm"));
+    } else {
+      result.setDescription(getQuery().toString() + " doesn't match id " + doc);
+      result.setValue(0);
+      result.setMatch(Boolean.FALSE);
+    }
+    return result;
+  }
+
+  @Override
+  public final Scorer scorer(LeafReaderContext context, Bits acceptDocs) throws IOException {
+    return scorer(context, acceptDocs, queryWeight);
+  }
+
+  abstract Scorer scorer(LeafReaderContext context, Bits acceptDocs, float score) throws IOException;
+
+}

Modified: lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/search/DocValuesRangeQuery.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/search/DocValuesRangeQuery.java?rev=1662244&r1=1662243&r2=1662244&view=diff
==============================================================================
--- lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/search/DocValuesRangeQuery.java (original)
+++ lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/search/DocValuesRangeQuery.java Wed Feb 25 15:26:04 2015
@@ -123,45 +123,10 @@ public final class DocValuesRangeQuery e
     if (lowerVal == null && upperVal == null) {
       throw new IllegalStateException("Both min and max values cannot be null, call rewrite first");
     }
-    return new Weight(DocValuesRangeQuery.this) {
-
-      private float queryNorm;
-      private float queryWeight;
-
-      @Override
-      public float getValueForNormalization() throws IOException {
-        queryWeight = getBoost();
-        return queryWeight * queryWeight;
-      }
-
-      @Override
-      public void normalize(float norm, float topLevelBoost) {
-        queryNorm = norm * topLevelBoost;
-        queryWeight *= queryNorm;
-      }
-
-      @Override
-      public Explanation explain(LeafReaderContext context, int doc) throws IOException {
-        final Scorer s = scorer(context, context.reader().getLiveDocs());
-        final boolean exists = (s != null && s.advance(doc) == doc);
-
-        final ComplexExplanation result = new ComplexExplanation();
-        if (exists) {
-          result.setDescription(DocValuesRangeQuery.this.toString() + ", product of:");
-          result.setValue(queryWeight);
-          result.setMatch(Boolean.TRUE);
-          result.addDetail(new Explanation(getBoost(), "boost"));
-          result.addDetail(new Explanation(queryNorm, "queryNorm"));
-        } else {
-          result.setDescription(DocValuesRangeQuery.this.toString() + " doesn't match id " + doc);
-          result.setValue(0);
-          result.setMatch(Boolean.FALSE);
-        }
-        return result;
-      }
+    return new ConstantScoreWeight(DocValuesRangeQuery.this) {
 
       @Override
-      public Scorer scorer(LeafReaderContext context, Bits acceptDocs) throws IOException {
+      public Scorer scorer(LeafReaderContext context, Bits acceptDocs, float score) throws IOException {
 
         final Bits docsWithField = context.reader().getDocsWithField(field);
         if (docsWithField == null || docsWithField instanceof MatchNoBits) {
@@ -240,7 +205,7 @@ public final class DocValuesRangeQuery e
           throw new AssertionError();
         }
 
-        return new RangeScorer(this, twoPhaseRange, queryWeight);
+        return new RangeScorer(this, twoPhaseRange, score);
       }
 
     };

Added: lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/search/DocValuesTermsQuery.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/search/DocValuesTermsQuery.java?rev=1662244&view=auto
==============================================================================
--- lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/search/DocValuesTermsQuery.java (added)
+++ lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/search/DocValuesTermsQuery.java Wed Feb 25 15:26:04 2015
@@ -0,0 +1,227 @@
+package org.apache.lucene.search;
+
+/*
+ * 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 java.util.AbstractList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Objects;
+
+import org.apache.lucene.index.DocValues;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.LeafReaderContext;
+import org.apache.lucene.index.SortedSetDocValues;
+import org.apache.lucene.util.ArrayUtil;
+import org.apache.lucene.util.Bits;
+import org.apache.lucene.util.BytesRef;
+import org.apache.lucene.util.FixedBitSet;
+import org.apache.lucene.util.LongBitSet;
+
+/**
+ * A {@link Query} that only accepts documents whose
+ * term value in the specified field is contained in the
+ * provided set of allowed terms.
+ *
+ * <p>
+ * This is the same functionality as TermsQuery (from
+ * queries/), but because of drastically different
+ * implementations, they also have different performance
+ * characteristics, as described below.
+ *
+ * <p>
+ * With each search, this query translates the specified
+ * set of Terms into a private {@link LongBitSet} keyed by
+ * term number per unique {@link IndexReader} (normally one
+ * reader per segment).  Then, during matching, the term
+ * number for each docID is retrieved from the cache and
+ * then checked for inclusion using the {@link LongBitSet}.
+ * Since all testing is done using RAM resident data
+ * structures, performance should be very fast, most likely
+ * fast enough to not require further caching of the
+ * DocIdSet for each possible combination of terms.
+ * However, because docIDs are simply scanned linearly, an
+ * index with a great many small documents may find this
+ * linear scan too costly.
+ *
+ * <p>
+ * In contrast, TermsQuery builds up an {@link FixedBitSet},
+ * keyed by docID, every time it's created, by enumerating
+ * through all matching docs using {@link org.apache.lucene.index.PostingsEnum} to seek
+ * and scan through each term's docID list.  While there is
+ * no linear scan of all docIDs, besides the allocation of
+ * the underlying array in the {@link FixedBitSet}, this
+ * approach requires a number of "disk seeks" in proportion
+ * to the number of terms, which can be exceptionally costly
+ * when there are cache misses in the OS's IO cache.
+ *
+ * <p>
+ * Generally, this filter will be slower on the first
+ * invocation for a given field, but subsequent invocations,
+ * even if you change the allowed set of Terms, should be
+ * faster than TermsQuery, especially as the number of
+ * Terms being matched increases.  If you are matching only
+ * a very small number of terms, and those terms in turn
+ * match a very small number of documents, TermsQuery may
+ * perform faster.
+ *
+ * <p>
+ * Which query is best is very application dependent.
+ */
+public class DocValuesTermsQuery extends Query {
+
+  private final String field;
+  private final BytesRef[] terms;
+
+  public DocValuesTermsQuery(String field, Collection<BytesRef> terms) {
+    this.field = Objects.requireNonNull(field);
+    this.terms = terms.toArray(new BytesRef[terms.size()]);
+    ArrayUtil.timSort(this.terms, BytesRef.getUTF8SortedAsUnicodeComparator());
+  }
+
+  public DocValuesTermsQuery(String field, BytesRef... terms) {
+    this(field, Arrays.asList(terms));
+  }
+
+  public DocValuesTermsQuery(String field, String... terms) {
+    this(field, new AbstractList<BytesRef>() {
+      @Override
+      public BytesRef get(int index) {
+        return new BytesRef(terms[index]);
+      }
+      @Override
+      public int size() {
+        return terms.length;
+      }
+    });
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (obj instanceof DocValuesTermsQuery == false) {
+      return false;
+    }
+    DocValuesTermsQuery that = (DocValuesTermsQuery) obj;
+    if (!field.equals(that.field)) {
+      return false;
+    }
+    if (getBoost() != that.getBoost()) {
+      return false;
+    }
+    return Arrays.equals(terms, that.terms);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(field, Arrays.asList(terms), getBoost());
+  }
+
+  @Override
+  public String toString(String defaultField) {
+    StringBuilder sb = new StringBuilder();
+    sb.append(field).append(": [");
+    for (BytesRef term : terms) {
+      sb.append(term).append(", ");
+    }
+    if (terms.length > 0) {
+      sb.setLength(sb.length() - 2);
+    }
+    return sb.append(']').toString();
+  }
+
+  @Override
+  public Weight createWeight(IndexSearcher searcher, boolean needsScores) throws IOException {
+    return new ConstantScoreWeight(this) {
+
+      @Override
+      Scorer scorer(LeafReaderContext context, Bits acceptDocs, float score) throws IOException {
+        final SortedSetDocValues values = DocValues.getSortedSet(context.reader(), field);
+        final LongBitSet bits = new LongBitSet(values.getValueCount());
+        for (BytesRef term : terms) {
+          final long ord = values.lookupTerm(term);
+          if (ord >= 0) {
+            bits.set(ord);
+          }
+        }
+
+        final DocIdSetIterator approximation = DocIdSetIterator.all(context.reader().maxDoc());
+        final TwoPhaseDocIdSetIterator twoPhaseIterator = new TwoPhaseDocIdSetIterator() {
+          @Override
+          public DocIdSetIterator approximation() {
+            return approximation;
+          }
+          @Override
+          public boolean matches() throws IOException {
+            final int doc = approximation.docID();
+            if (acceptDocs != null && acceptDocs.get(doc) == false) {
+              return false;
+            }
+            values.setDocument(doc);
+            for (long ord = values.nextOrd(); ord != SortedSetDocValues.NO_MORE_ORDS; ord = values.nextOrd()) {
+              if (bits.get(ord)) {
+                return true;
+              }
+            }
+            return false;
+          }
+        };
+        final DocIdSetIterator disi = TwoPhaseDocIdSetIterator.asDocIdSetIterator(twoPhaseIterator);
+        return new Scorer(this) {
+
+          @Override
+          public TwoPhaseDocIdSetIterator asTwoPhaseIterator() {
+            return twoPhaseIterator;
+          }
+
+          @Override
+          public float score() throws IOException {
+            return score;
+          }
+
+          @Override
+          public int freq() throws IOException {
+            return 1;
+          }
+
+          @Override
+          public int docID() {
+            return disi.docID();
+          }
+
+          @Override
+          public int nextDoc() throws IOException {
+            return disi.nextDoc();
+          }
+
+          @Override
+          public int advance(int target) throws IOException {
+            return disi.advance(target);
+          }
+
+          @Override
+          public long cost() {
+            return disi.cost();
+          }
+
+        };
+      }
+
+    };
+  }
+
+}

Modified: lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/search/FieldValueQuery.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/search/FieldValueQuery.java?rev=1662244&r1=1662243&r2=1662244&view=diff
==============================================================================
--- lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/search/FieldValueQuery.java (original)
+++ lucene/dev/trunk/lucene/core/src/java/org/apache/lucene/search/FieldValueQuery.java Wed Feb 25 15:26:04 2015
@@ -61,45 +61,10 @@ public final class FieldValueQuery exten
 
   @Override
   public Weight createWeight(IndexSearcher searcher, boolean needsScores) throws IOException {
-    return new Weight(this) {
-
-      private float queryNorm;
-      private float queryWeight;
-
-      @Override
-      public float getValueForNormalization() throws IOException {
-        queryWeight = getBoost();
-        return queryWeight * queryWeight;
-      }
-
-      @Override
-      public void normalize(float norm, float topLevelBoost) {
-        queryNorm = norm * topLevelBoost;
-        queryWeight *= queryNorm;
-      }
-
-      @Override
-      public Explanation explain(LeafReaderContext context, int doc) throws IOException {
-        final Scorer s = scorer(context, context.reader().getLiveDocs());
-        final boolean exists = (s != null && s.advance(doc) == doc);
-
-        final ComplexExplanation result = new ComplexExplanation();
-        if (exists) {
-          result.setDescription(FieldValueQuery.this.toString() + ", product of:");
-          result.setValue(queryWeight);
-          result.setMatch(Boolean.TRUE);
-          result.addDetail(new Explanation(getBoost(), "boost"));
-          result.addDetail(new Explanation(queryNorm, "queryNorm"));
-        } else {
-          result.setDescription(FieldValueQuery.this.toString() + " doesn't match id " + doc);
-          result.setValue(0);
-          result.setMatch(Boolean.FALSE);
-        }
-        return result;
-      }
+    return new ConstantScoreWeight(this) {
 
       @Override
-      public Scorer scorer(LeafReaderContext context, Bits acceptDocs) throws IOException {
+      public Scorer scorer(LeafReaderContext context, Bits acceptDocs, float score) throws IOException {
         final Bits docsWithField = context.reader().getDocsWithField(field);
         if (docsWithField == null || docsWithField instanceof MatchNoBits) {
           return null;
@@ -161,7 +126,7 @@ public final class FieldValueQuery exten
 
           @Override
           public float score() throws IOException {
-            return queryWeight;
+            return score;
           }
         };
       }

Modified: lucene/dev/trunk/lucene/core/src/test/org/apache/lucene/search/TestComplexExplanations.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/lucene/core/src/test/org/apache/lucene/search/TestComplexExplanations.java?rev=1662244&r1=1662243&r2=1662244&view=diff
==============================================================================
--- lucene/dev/trunk/lucene/core/src/test/org/apache/lucene/search/TestComplexExplanations.java (original)
+++ lucene/dev/trunk/lucene/core/src/test/org/apache/lucene/search/TestComplexExplanations.java Wed Feb 25 15:26:04 2015
@@ -71,13 +71,13 @@ public class TestComplexExplanations ext
           Occur.SHOULD);
     q.add(snear(sf("w3",2), st("w2"), st("w3"), 5, true),
           Occur.SHOULD);
-    
+
     Query t = new FilteredQuery(new TermQuery(new Term(FIELD, "xx")),
-                                new ItemizedFilter(new int[] {1,3}));
+                                new QueryWrapperFilter(new ItemizedQuery(new int[] {1,3})));
     t.setBoost(1000);
     q.add(t, Occur.SHOULD);
     
-    t = new ConstantScoreQuery(new ItemizedFilter(new int[] {0,2}));
+    t = new ConstantScoreQuery(new ItemizedQuery(new int[] {0,2}));
     t.setBoost(30);
     q.add(t, Occur.SHOULD);
     
@@ -136,11 +136,11 @@ public class TestComplexExplanations ext
           Occur.SHOULD);
     
     Query t = new FilteredQuery(new TermQuery(new Term(FIELD, "xx")),
-                                new ItemizedFilter(new int[] {1,3}));
+                                new QueryWrapperFilter(new ItemizedQuery(new int[] {1,3})));
     t.setBoost(1000);
     q.add(t, Occur.SHOULD);
     
-    t = new ConstantScoreQuery(new ItemizedFilter(new int[] {0,2}));
+    t = new ConstantScoreQuery(new ItemizedQuery(new int[] {0,2}));
     t.setBoost(-20.0f);
     q.add(t, Occur.SHOULD);
     
@@ -207,13 +207,11 @@ public class TestComplexExplanations ext
   public void testFQ5() throws Exception {
     TermQuery query = new TermQuery(new Term(FIELD, "xx"));
     query.setBoost(0);
-    bqtest(new FilteredQuery(query,
-                             new ItemizedFilter(new int[] {1,3})),
-           new int[] {3});
+    bqtest(new FilteredQuery(query, new QueryWrapperFilter(new ItemizedQuery(new int[] {1,3}))), new int[] {3});
   }
   
   public void testCSQ4() throws Exception {
-    Query q = new ConstantScoreQuery(new ItemizedFilter(new int[] {3}));
+    Query q = new ConstantScoreQuery(new ItemizedQuery(new int[] {3}));
     q.setBoost(0);
     bqtest(q, new int[] {3});
   }

Added: lucene/dev/trunk/lucene/core/src/test/org/apache/lucene/search/TestDocValuesTermsQuery.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/lucene/core/src/test/org/apache/lucene/search/TestDocValuesTermsQuery.java?rev=1662244&view=auto
==============================================================================
--- lucene/dev/trunk/lucene/core/src/test/org/apache/lucene/search/TestDocValuesTermsQuery.java (added)
+++ lucene/dev/trunk/lucene/core/src/test/org/apache/lucene/search/TestDocValuesTermsQuery.java Wed Feb 25 15:26:04 2015
@@ -0,0 +1,188 @@
+package org.apache.lucene.search;
+
+/*
+ * 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 java.util.ArrayList;
+import java.util.List;
+
+import org.apache.lucene.document.Document;
+import org.apache.lucene.document.Field.Store;
+import org.apache.lucene.document.SortedDocValuesField;
+import org.apache.lucene.document.StringField;
+import org.apache.lucene.index.IndexReader;
+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.BytesRef;
+import org.apache.lucene.util.IOUtils;
+import org.apache.lucene.util.LuceneTestCase;
+import org.apache.lucene.util.TestUtil;
+
+public class TestDocValuesTermsQuery extends LuceneTestCase {
+
+  public void testEquals() {
+    assertEquals(new DocValuesTermsQuery("foo", "bar"), new DocValuesTermsQuery("foo", "bar"));
+    assertEquals(new DocValuesTermsQuery("foo", "bar", "baz"), new DocValuesTermsQuery("foo", "baz", "bar"));
+    assertFalse(new DocValuesTermsQuery("foo", "bar").equals(new DocValuesTermsQuery("foo2", "bar")));
+    assertFalse(new DocValuesTermsQuery("foo", "bar").equals(new DocValuesTermsQuery("foo", "baz")));
+  }
+
+  public void testDuelTermsQuery() throws IOException {
+    final int iters = atLeast(2);
+    for (int iter = 0; iter < iters; ++iter) {
+      final List<Term> allTerms = new ArrayList<>();
+      final int numTerms = TestUtil.nextInt(random(), 1, 1 << TestUtil.nextInt(random(), 1, 10));
+      for (int i = 0; i < numTerms; ++i) {
+        final String value = TestUtil.randomAnalysisString(random(), 10, true);
+        allTerms.add(new Term("f", value));
+      }
+      Directory dir = newDirectory();
+      RandomIndexWriter iw = new RandomIndexWriter(random(), dir);
+      final int numDocs = atLeast(100);
+      for (int i = 0; i < numDocs; ++i) {
+        Document doc = new Document();
+        final Term term = allTerms.get(random().nextInt(allTerms.size()));
+        doc.add(new StringField(term.field(), term.text(), Store.NO));
+        doc.add(new SortedDocValuesField(term.field(), new BytesRef(term.text())));
+        iw.addDocument(doc);
+      }
+      if (numTerms > 1 && random().nextBoolean()) {
+        iw.deleteDocuments(new TermQuery(allTerms.get(0)));
+      }
+      iw.commit();
+      final IndexReader reader = iw.getReader();
+      final IndexSearcher searcher = newSearcher(reader);
+      iw.close();
+
+      if (reader.numDocs() == 0) {
+        // may occasionally happen if all documents got the same term
+        IOUtils.close(reader, dir);
+        continue;
+      }
+
+      for (int i = 0; i < 100; ++i) {
+        final float boost = random().nextFloat() * 10;
+        final int numQueryTerms = TestUtil.nextInt(random(), 1, 1 << TestUtil.nextInt(random(), 1, 8));
+        List<Term> queryTerms = new ArrayList<>();
+        for (int j = 0; j < numQueryTerms; ++j) {
+          queryTerms.add(allTerms.get(random().nextInt(allTerms.size())));
+        }
+        final BooleanQuery bq = new BooleanQuery();
+        for (Term term : queryTerms) {
+          bq.add(new TermQuery(term), Occur.SHOULD);
+        }
+        Query q1 = new ConstantScoreQuery(bq);
+        q1.setBoost(boost);
+        List<String> bytesTerms = new ArrayList<>();
+        for (Term term : queryTerms) {
+          bytesTerms.add(term.text());
+        }
+        final Query q2 = new DocValuesTermsQuery("f", bytesTerms.toArray(new String[0]));
+        q2.setBoost(boost);
+        assertSameMatches(searcher, q1, q2, true);
+      }
+
+      reader.close();
+      dir.close();
+    }
+  }
+
+  public void testApproximation() throws IOException {
+    final int iters = atLeast(2);
+    for (int iter = 0; iter < iters; ++iter) {
+      final List<Term> allTerms = new ArrayList<>();
+      final int numTerms = TestUtil.nextInt(random(), 1, 1 << TestUtil.nextInt(random(), 1, 10));
+      for (int i = 0; i < numTerms; ++i) {
+        final String value = TestUtil.randomAnalysisString(random(), 10, true);
+        allTerms.add(new Term("f", value));
+      }
+      Directory dir = newDirectory();
+      RandomIndexWriter iw = new RandomIndexWriter(random(), dir);
+      final int numDocs = atLeast(100);
+      for (int i = 0; i < numDocs; ++i) {
+        Document doc = new Document();
+        final Term term = allTerms.get(random().nextInt(allTerms.size()));
+        doc.add(new StringField(term.field(), term.text(), Store.NO));
+        doc.add(new SortedDocValuesField(term.field(), new BytesRef(term.text())));
+        iw.addDocument(doc);
+      }
+      if (numTerms > 1 && random().nextBoolean()) {
+        iw.deleteDocuments(new TermQuery(allTerms.get(0)));
+      }
+      iw.commit();
+      final IndexReader reader = iw.getReader();
+      final IndexSearcher searcher = newSearcher(reader);
+      iw.close();
+
+      if (reader.numDocs() == 0) {
+        // may occasionally happen if all documents got the same term
+        IOUtils.close(reader, dir);
+        continue;
+      }
+
+      for (int i = 0; i < 100; ++i) {
+        final float boost = random().nextFloat() * 10;
+        final int numQueryTerms = TestUtil.nextInt(random(), 1, 1 << TestUtil.nextInt(random(), 1, 8));
+        List<Term> queryTerms = new ArrayList<>();
+        for (int j = 0; j < numQueryTerms; ++j) {
+          queryTerms.add(allTerms.get(random().nextInt(allTerms.size())));
+        }
+        final BooleanQuery bq = new BooleanQuery();
+        for (Term term : queryTerms) {
+          bq.add(new TermQuery(term), Occur.SHOULD);
+        }
+        Query q1 = new ConstantScoreQuery(bq);
+        q1.setBoost(boost);
+        List<String> bytesTerms = new ArrayList<>();
+        for (Term term : queryTerms) {
+          bytesTerms.add(term.text());
+        }
+        final Query q2 = new DocValuesTermsQuery("f", bytesTerms.toArray(new String[0]));
+        q2.setBoost(boost);
+
+        BooleanQuery bq1 = new BooleanQuery();
+        bq1.add(q1, Occur.MUST);
+        bq1.add(new TermQuery(allTerms.get(0)), Occur.FILTER);
+
+        BooleanQuery bq2 = new BooleanQuery();
+        bq2.add(q2, Occur.MUST);
+        bq2.add(new TermQuery(allTerms.get(0)), Occur.FILTER);
+
+        assertSameMatches(searcher, bq1, bq2, true);
+      }
+
+      reader.close();
+      dir.close();
+    }
+  }
+
+  private void assertSameMatches(IndexSearcher searcher, Query q1, Query q2, boolean scores) throws IOException {
+    final int maxDoc = searcher.getIndexReader().maxDoc();
+    final TopDocs td1 = searcher.search(q1, maxDoc, scores ? Sort.RELEVANCE : Sort.INDEXORDER);
+    final TopDocs td2 = searcher.search(q2, maxDoc, scores ? Sort.RELEVANCE : Sort.INDEXORDER);
+    assertEquals(td1.totalHits, td2.totalHits);
+    for (int i = 0; i < td1.scoreDocs.length; ++i) {
+      assertEquals(td1.scoreDocs[i].doc, td2.scoreDocs[i].doc);
+      if (scores) {
+        assertEquals(td1.scoreDocs[i].score, td2.scoreDocs[i].score, 10e-7);
+      }
+    }
+  }
+}

Modified: lucene/dev/trunk/lucene/core/src/test/org/apache/lucene/search/TestFieldCacheTermsFilter.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/lucene/core/src/test/org/apache/lucene/search/TestFieldCacheTermsFilter.java?rev=1662244&r1=1662243&r2=1662244&view=diff
==============================================================================
--- lucene/dev/trunk/lucene/core/src/test/org/apache/lucene/search/TestFieldCacheTermsFilter.java (original)
+++ lucene/dev/trunk/lucene/core/src/test/org/apache/lucene/search/TestFieldCacheTermsFilter.java Wed Feb 25 15:26:04 2015
@@ -32,7 +32,7 @@ import java.util.List;
 /**
  * A basic unit test for FieldCacheTermsFilter
  *
- * @see org.apache.lucene.search.DocValuesTermsFilter
+ * @see org.apache.lucene.search.DocValuesTermsQuery
  */
 public class TestFieldCacheTermsFilter extends LuceneTestCase {
   public void testMissingTerms() throws Exception {
@@ -52,22 +52,21 @@ public class TestFieldCacheTermsFilter e
     IndexSearcher searcher = newSearcher(reader);
     int numDocs = reader.numDocs();
     ScoreDoc[] results;
-    MatchAllDocsQuery q = new MatchAllDocsQuery();
 
     List<String> terms = new ArrayList<>();
     terms.add("5");
-    results = searcher.search(new FilteredQuery(q, new DocValuesTermsFilter(fieldName,  terms.toArray(new String[0]))), numDocs).scoreDocs;
+    results = searcher.search(new DocValuesTermsQuery(fieldName,  terms.toArray(new String[0])), numDocs).scoreDocs;
     assertEquals("Must match nothing", 0, results.length);
 
     terms = new ArrayList<>();
     terms.add("10");
-    results = searcher.search(new FilteredQuery(q, new DocValuesTermsFilter(fieldName,  terms.toArray(new String[0]))), numDocs).scoreDocs;
+    results = searcher.search(new DocValuesTermsQuery(fieldName,  terms.toArray(new String[0])), numDocs).scoreDocs;
     assertEquals("Must match 1", 1, results.length);
 
     terms = new ArrayList<>();
     terms.add("10");
     terms.add("20");
-    results = searcher.search(new FilteredQuery(q, new DocValuesTermsFilter(fieldName,  terms.toArray(new String[0]))), numDocs).scoreDocs;
+    results = searcher.search(new DocValuesTermsQuery(fieldName,  terms.toArray(new String[0])), numDocs).scoreDocs;
     assertEquals("Must match 2", 2, results.length);
 
     reader.close();

Modified: lucene/dev/trunk/lucene/core/src/test/org/apache/lucene/search/TestSimpleExplanations.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/lucene/core/src/test/org/apache/lucene/search/TestSimpleExplanations.java?rev=1662244&r1=1662243&r2=1662244&view=diff
==============================================================================
--- lucene/dev/trunk/lucene/core/src/test/org/apache/lucene/search/TestSimpleExplanations.java (original)
+++ lucene/dev/trunk/lucene/core/src/test/org/apache/lucene/search/TestSimpleExplanations.java Wed Feb 25 15:26:04 2015
@@ -105,28 +105,28 @@ public class TestSimpleExplanations exte
   
   public void testFQ1() throws Exception {
     qtest(new FilteredQuery(new TermQuery(new Term(FIELD, "w1")),
-                            new ItemizedFilter(new int[] {0,1,2,3})),
+                            new QueryWrapperFilter(new ItemizedQuery(new int[] {0,1,2,3}))),
           new int[] {0,1,2,3});
   }
   public void testFQ2() throws Exception {
     qtest(new FilteredQuery(new TermQuery(new Term(FIELD, "w1")),
-                            new ItemizedFilter(new int[] {0,2,3})),
+                            new QueryWrapperFilter(new ItemizedQuery(new int[] {0,2,3}))),
           new int[] {0,2,3});
   }
   public void testFQ3() throws Exception {
     qtest(new FilteredQuery(new TermQuery(new Term(FIELD, "xx")),
-                            new ItemizedFilter(new int[] {1,3})),
+                            new QueryWrapperFilter(new ItemizedQuery(new int[] {1,3}))),
           new int[] {3});
   }
   public void testFQ4() throws Exception {
     TermQuery termQuery = new TermQuery(new Term(FIELD, "xx"));
     termQuery.setBoost(1000);
-    qtest(new FilteredQuery(termQuery, new ItemizedFilter(new int[] {1,3})),
+    qtest(new FilteredQuery(termQuery, new QueryWrapperFilter(new ItemizedQuery(new int[] {1,3}))),
           new int[] {3});
   }
   public void testFQ6() throws Exception {
     Query q = new FilteredQuery(new TermQuery(new Term(FIELD, "xx")),
-                                new ItemizedFilter(new int[] {1,3}));
+                                new QueryWrapperFilter(new ItemizedQuery(new int[] {1,3})));
     q.setBoost(1000);
     qtest(q, new int[] {3});
   }
@@ -134,15 +134,15 @@ public class TestSimpleExplanations exte
   /* ConstantScoreQueries */
   
   public void testCSQ1() throws Exception {
-    Query q = new ConstantScoreQuery(new ItemizedFilter(new int[] {0,1,2,3}));
+    Query q = new ConstantScoreQuery(new ItemizedQuery(new int[] {0,1,2,3}));
     qtest(q, new int[] {0,1,2,3});
   }
   public void testCSQ2() throws Exception {
-    Query q = new ConstantScoreQuery(new ItemizedFilter(new int[] {1,3}));
+    Query q = new ConstantScoreQuery(new ItemizedQuery(new int[] {1,3}));
     qtest(q, new int[] {1,3});
   }
   public void testCSQ3() throws Exception {
-    Query q = new ConstantScoreQuery(new ItemizedFilter(new int[] {0,2}));
+    Query q = new ConstantScoreQuery(new ItemizedQuery(new int[] {0,2}));
     q.setBoost(1000);
     qtest(q, new int[] {0,2});
   }

Modified: lucene/dev/trunk/lucene/test-framework/src/java/org/apache/lucene/search/BaseExplanationTestCase.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/lucene/test-framework/src/java/org/apache/lucene/search/BaseExplanationTestCase.java?rev=1662244&r1=1662243&r2=1662244&view=diff
==============================================================================
--- lucene/dev/trunk/lucene/test-framework/src/java/org/apache/lucene/search/BaseExplanationTestCase.java (original)
+++ lucene/dev/trunk/lucene/test-framework/src/java/org/apache/lucene/search/BaseExplanationTestCase.java Wed Feb 25 15:26:04 2015
@@ -112,7 +112,7 @@ public abstract class BaseExplanationTes
   /** 
    * Convenience subclass of FieldCacheTermsFilter
    */
-  public static class ItemizedFilter extends DocValuesTermsFilter {
+  public static class ItemizedQuery extends DocValuesTermsQuery {
     private static String[] int2str(int [] terms) {
       String [] out = new String[terms.length];
       for (int i = 0; i < terms.length; i++) {
@@ -120,7 +120,7 @@ public abstract class BaseExplanationTes
       }
       return out;
     }
-    public ItemizedFilter(int [] keys) {
+    public ItemizedQuery(int [] keys) {
       super(KEY, int2str(keys));
     }
   }

Modified: lucene/dev/trunk/solr/core/src/java/org/apache/solr/search/TermsQParserPlugin.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/java/org/apache/solr/search/TermsQParserPlugin.java?rev=1662244&r1=1662243&r2=1662244&view=diff
==============================================================================
--- lucene/dev/trunk/solr/core/src/java/org/apache/solr/search/TermsQParserPlugin.java (original)
+++ lucene/dev/trunk/solr/core/src/java/org/apache/solr/search/TermsQParserPlugin.java Wed Feb 25 15:26:04 2015
@@ -25,7 +25,7 @@ import org.apache.lucene.queries.TermsQu
 import org.apache.lucene.search.AutomatonQuery;
 import org.apache.lucene.search.BooleanClause;
 import org.apache.lucene.search.BooleanQuery;
-import org.apache.lucene.search.DocValuesTermsFilter;
+import org.apache.lucene.search.DocValuesTermsQuery;
 import org.apache.lucene.search.Filter;
 import org.apache.lucene.search.MultiTermQueryWrapperFilter;
 import org.apache.lucene.search.Query;
@@ -93,7 +93,7 @@ public class TermsQParserPlugin extends
       //note: limited to one val per doc
       @Override
       Filter makeFilter(String fname, BytesRef[] byteRefs) {
-        return new DocValuesTermsFilter(fname, byteRefs);
+        return new QueryWrapperFilter(new DocValuesTermsQuery(fname, byteRefs));
       }
     };