You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by us...@apache.org on 2010/11/15 00:13:47 UTC

svn commit: r1035096 [2/2] - in /lucene/dev/trunk/lucene: ./ contrib/ contrib/queries/src/java/org/apache/lucene/search/ contrib/queries/src/java/org/apache/lucene/search/regex/ contrib/queries/src/test/org/apache/lucene/search/regex/ contrib/spellchec...

Added: lucene/dev/trunk/lucene/src/java/org/apache/lucene/search/TopTermsRewrite.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/lucene/src/java/org/apache/lucene/search/TopTermsRewrite.java?rev=1035096&view=auto
==============================================================================
--- lucene/dev/trunk/lucene/src/java/org/apache/lucene/search/TopTermsRewrite.java (added)
+++ lucene/dev/trunk/lucene/src/java/org/apache/lucene/search/TopTermsRewrite.java Sun Nov 14 23:13:46 2010
@@ -0,0 +1,182 @@
+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.HashMap;
+import java.util.Map;
+import java.util.PriorityQueue;
+import java.util.Comparator;
+
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.index.TermsEnum;
+import org.apache.lucene.util.ArrayUtil;
+import org.apache.lucene.util.BytesRef;
+
+/**
+ * Base rewrite method for collecting only the top terms
+ * via a priority queue.
+ * @lucene.internal Only public to be accessible by spans package.
+ */
+public abstract class TopTermsRewrite<Q extends Query> extends TermCollectingRewrite<Q> {
+
+  private final int size;
+  
+  /** 
+   * Create a TopTermsBooleanQueryRewrite for 
+   * at most <code>size</code> terms.
+   * <p>
+   * NOTE: if {@link BooleanQuery#getMaxClauseCount} is smaller than 
+   * <code>size</code>, then it will be used instead. 
+   */
+  public TopTermsRewrite(int size) {
+    this.size = size;
+  }
+  
+  /** return the maximum priority queue size */
+  public int getSize() {
+    return size;
+  }
+  
+  /** return the maximum size of the priority queue (for boolean rewrites this is BooleanQuery#getMaxClauseCount). */
+  protected abstract int getMaxSize();
+  
+  @Override
+  public final Q rewrite(final IndexReader reader, final MultiTermQuery query) throws IOException {
+    final int maxSize = Math.min(size, getMaxSize());
+    final PriorityQueue<ScoreTerm> stQueue = new PriorityQueue<ScoreTerm>();
+    collectTerms(reader, query, new TermCollector() {
+      private final MaxNonCompetitiveBoostAttribute maxBoostAtt =
+        attributes.addAttribute(MaxNonCompetitiveBoostAttribute.class);
+      
+      private final Map<BytesRef,ScoreTerm> visitedTerms = new HashMap<BytesRef,ScoreTerm>();
+      
+      private TermsEnum termsEnum;
+      private Comparator<BytesRef> termComp;
+      private BoostAttribute boostAtt;        
+      private ScoreTerm st;
+      
+      @Override
+      public void setNextEnum(TermsEnum termsEnum) throws IOException {
+        this.termsEnum = termsEnum;
+        this.termComp = termsEnum.getComparator();
+        // lazy init the initial ScoreTerm because comparator is not known on ctor:
+        if (st == null)
+          st = new ScoreTerm(this.termComp);
+        boostAtt = termsEnum.attributes().addAttribute(BoostAttribute.class);
+      }
+    
+      @Override
+      public boolean collect(BytesRef bytes) {
+        final float boost = boostAtt.getBoost();
+        // ignore uncompetetive hits
+        if (stQueue.size() == maxSize) {
+          final ScoreTerm t = stQueue.peek();
+          if (boost < t.boost)
+            return true;
+          if (boost == t.boost && termComp.compare(bytes, t.bytes) > 0)
+            return true;
+        }
+        ScoreTerm t = visitedTerms.get(bytes);
+        if (t != null) {
+          // if the term is already in the PQ, only update docFreq of term in PQ
+          t.docFreq += termsEnum.docFreq();
+          assert t.boost == boost : "boost should be equal in all segment TermsEnums";
+        } else {
+          // add new entry in PQ, we must clone the term, else it may get overwritten!
+          st.bytes.copy(bytes);
+          st.boost = boost;
+          st.docFreq = termsEnum.docFreq();
+          visitedTerms.put(st.bytes, st);
+          stQueue.offer(st);
+          // possibly drop entries from queue
+          if (stQueue.size() > maxSize) {
+            st = stQueue.poll();
+            visitedTerms.remove(st.bytes);
+          } else {
+            st = new ScoreTerm(termComp);
+          }
+          assert stQueue.size() <= maxSize : "the PQ size must be limited to maxSize";
+          // set maxBoostAtt with values to help FuzzyTermsEnum to optimize
+          if (stQueue.size() == maxSize) {
+            t = stQueue.peek();
+            maxBoostAtt.setMaxNonCompetitiveBoost(t.boost);
+            maxBoostAtt.setCompetitiveTerm(t.bytes);
+          }
+        }
+        return true;
+      }
+    });
+    
+    final Term placeholderTerm = new Term(query.field);
+    final Q q = getTopLevelQuery();
+    final ScoreTerm[] scoreTerms = stQueue.toArray(new ScoreTerm[stQueue.size()]);
+    ArrayUtil.quickSort(scoreTerms, scoreTermSortByTermComp);
+    for (final ScoreTerm st : scoreTerms) {
+      final Term term = placeholderTerm.createTerm(st.bytes);
+      assert reader.docFreq(term) == st.docFreq;
+      addClause(q, term, st.docFreq, query.getBoost() * st.boost); // add to query
+    }
+    query.incTotalNumberOfTerms(scoreTerms.length);
+    return q;
+  }
+
+  @Override
+  public int hashCode() {
+    return 31 * size;
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (this == obj) return true;
+    if (obj == null) return false;
+    if (getClass() != obj.getClass()) return false;
+    final TopTermsRewrite other = (TopTermsRewrite) obj;
+    if (size != other.size) return false;
+    return true;
+  }
+  
+  private static final Comparator<ScoreTerm> scoreTermSortByTermComp = 
+    new Comparator<ScoreTerm>() {
+      public int compare(ScoreTerm st1, ScoreTerm st2) {
+        assert st1.termComp == st2.termComp :
+          "term comparator should not change between segments";
+        return st1.termComp.compare(st1.bytes, st2.bytes);
+      }
+    };
+
+  static final class ScoreTerm implements Comparable<ScoreTerm> {
+    public final Comparator<BytesRef> termComp;
+
+    public final BytesRef bytes = new BytesRef();
+    public float boost;
+    public int docFreq;
+    
+    public ScoreTerm(Comparator<BytesRef> termComp) {
+      this.termComp = termComp;
+    }
+    
+    public int compareTo(ScoreTerm other) {
+      if (this.boost == other.boost)
+        return termComp.compare(other.bytes, this.bytes);
+      else
+        return Float.compare(this.boost, other.boost);
+    }
+  }
+}

Propchange: lucene/dev/trunk/lucene/src/java/org/apache/lucene/search/TopTermsRewrite.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: lucene/dev/trunk/lucene/src/java/org/apache/lucene/search/TopTermsRewrite.java
------------------------------------------------------------------------------
    svn:keywords = Date Author Id Revision HeadURL

Added: lucene/dev/trunk/lucene/src/java/org/apache/lucene/search/spans/SpanMultiTermQueryWrapper.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/lucene/src/java/org/apache/lucene/search/spans/SpanMultiTermQueryWrapper.java?rev=1035096&view=auto
==============================================================================
--- lucene/dev/trunk/lucene/src/java/org/apache/lucene/search/spans/SpanMultiTermQueryWrapper.java (added)
+++ lucene/dev/trunk/lucene/src/java/org/apache/lucene/search/spans/SpanMultiTermQueryWrapper.java Sun Nov 14 23:13:46 2010
@@ -0,0 +1,234 @@
+package org.apache.lucene.search.spans;
+
+/**
+ * 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.IndexReader;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.search.MultiTermQuery;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.TopTermsRewrite;
+import org.apache.lucene.search.ScoringRewrite;
+import org.apache.lucene.search.BooleanClause.Occur; // javadocs only
+
+/**
+ * Wraps any {@link MultiTermQuery} as a {@link SpanQuery}, 
+ * so it can be nested within other SpanQuery classes.
+ * <p>
+ * The query is rewritten by default to a {@link SpanOrQuery} containing
+ * the expanded terms, but this can be customized. 
+ * <p>
+ * Example:
+ * <blockquote><pre>
+ * {@code
+ * WildcardQuery wildcard = new WildcardQuery(new Term("field", "bro?n"));
+ * SpanQuery spanWildcard = new SpanMultiTermQueryWrapper<WildcardQuery>(wildcard);
+ * // do something with spanWildcard, such as use it in a SpanFirstQuery
+ * }
+ * </pre></blockquote>
+ */
+public class SpanMultiTermQueryWrapper<Q extends MultiTermQuery> extends SpanQuery {
+  protected final Q query;
+
+  /**
+   * Create a new SpanMultiTermQueryWrapper. 
+   * 
+   * @param query Query to wrap.
+   * <p>
+   * NOTE: This will call {@link MultiTermQuery#setRewriteMethod(MultiTermQuery.RewriteMethod)}
+   * on the wrapped <code>query</code>, changing its rewrite method to a suitable one for spans.
+   * Be sure to not change the rewrite method on the wrapped query afterwards! Doing so will
+   * throw {@link UnsupportedOperationException} on rewriting this query!
+   */
+  public SpanMultiTermQueryWrapper(Q query) {
+    this.query = query;
+    
+    MultiTermQuery.RewriteMethod method = query.getRewriteMethod();
+    if (method instanceof TopTermsRewrite) {
+      final int pqsize = ((TopTermsRewrite) method).getSize();
+      setRewriteMethod(new TopTermsSpanBooleanQueryRewrite(pqsize));
+    } else {
+      setRewriteMethod(SCORING_SPAN_QUERY_REWRITE); 
+    }
+  }
+  
+  /**
+   * Expert: returns the rewriteMethod
+   */
+  public final SpanRewriteMethod getRewriteMethod() {
+    final MultiTermQuery.RewriteMethod m = query.getRewriteMethod();
+    if (!(m instanceof SpanRewriteMethod))
+      throw new UnsupportedOperationException("You can only use SpanMultiTermQueryWrapper with a suitable SpanRewriteMethod.");
+    return (SpanRewriteMethod) m;
+  }
+
+  /**
+   * Expert: sets the rewrite method. This only makes sense
+   * to be a span rewrite method.
+   */
+  public final void setRewriteMethod(SpanRewriteMethod rewriteMethod) {
+    query.setRewriteMethod(rewriteMethod);
+  }
+  
+  @Override
+  public Spans getSpans(IndexReader reader) throws IOException {
+    throw new UnsupportedOperationException("Query should have been rewritten");
+  }
+
+  @Override
+  public String getField() {
+    return query.getField();
+  }
+
+  @Override
+  public String toString(String field) {
+    StringBuilder builder = new StringBuilder();
+    builder.append("SpanMultiTermQueryWrapper(");
+    builder.append(query.toString(field));
+    builder.append(")");
+    return builder.toString();
+  }
+
+  @Override
+  public Query rewrite(IndexReader reader) throws IOException {
+    final Query q = query.rewrite(reader);
+    if (!(q instanceof SpanQuery))
+      throw new UnsupportedOperationException("You can only use SpanMultiTermQueryWrapper with a suitable SpanRewriteMethod.");
+    return q;
+  }
+  
+  @Override
+  public int hashCode() {
+    return 31 * query.hashCode();
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (this == obj) return true;
+    if (obj == null) return false;
+    if (getClass() != obj.getClass()) return false;
+    final SpanMultiTermQueryWrapper other = (SpanMultiTermQueryWrapper) obj;
+    return query.equals(other.query);
+  }
+
+  /** Abstract class that defines how the query is rewritten. */
+  public static abstract class SpanRewriteMethod extends MultiTermQuery.RewriteMethod {
+    @Override
+    public abstract SpanQuery rewrite(IndexReader reader, MultiTermQuery query) throws IOException;
+  }
+
+  /**
+   * A rewrite method that first translates each term into a SpanTermQuery in a
+   * {@link Occur#SHOULD} clause in a BooleanQuery, and keeps the
+   * scores as computed by the query.
+   * 
+   * @see #setRewriteMethod
+   */
+  public final static SpanRewriteMethod SCORING_SPAN_QUERY_REWRITE = new SpanRewriteMethod() {
+    private final ScoringRewrite<SpanOrQuery> delegate = new ScoringRewrite<SpanOrQuery>() {
+      @Override
+      protected SpanOrQuery getTopLevelQuery() {
+        return new SpanOrQuery();
+      }
+
+      @Override
+      protected void addClause(SpanOrQuery topLevel, Term term, int docCount, float boost) {
+        final SpanTermQuery q = new SpanTermQuery(term);
+        q.setBoost(boost);
+        topLevel.addClause(q);
+      }
+    };
+    
+    @Override
+    public SpanQuery rewrite(IndexReader reader, MultiTermQuery query) throws IOException {
+      return delegate.rewrite(reader, query);
+    }
+
+    // Make sure we are still a singleton even after deserializing
+    protected Object readResolve() {
+      return SCORING_SPAN_QUERY_REWRITE;
+    }
+  };
+  
+  /**
+   * A rewrite method that first translates each term into a SpanTermQuery in a
+   * {@link Occur#SHOULD} clause in a BooleanQuery, and keeps the
+   * scores as computed by the query.
+   * 
+   * <p>
+   * This rewrite method only uses the top scoring terms so it will not overflow
+   * the boolean max clause count.
+   * 
+   * @see #setRewriteMethod
+   */
+  public static final class TopTermsSpanBooleanQueryRewrite extends SpanRewriteMethod  {
+    private final TopTermsRewrite<SpanOrQuery> delegate;
+  
+    /** 
+     * Create a TopTermsSpanBooleanQueryRewrite for 
+     * at most <code>size</code> terms.
+     */
+    public TopTermsSpanBooleanQueryRewrite(int size) {
+      delegate = new TopTermsRewrite<SpanOrQuery>(size) {
+        @Override
+        protected int getMaxSize() {
+          return Integer.MAX_VALUE;
+        }
+    
+        @Override
+        protected SpanOrQuery getTopLevelQuery() {
+          return new SpanOrQuery();
+        }
+
+        @Override
+        protected void addClause(SpanOrQuery topLevel, Term term, int docFreq, float boost) {
+          final SpanTermQuery q = new SpanTermQuery(term);
+          q.setBoost(boost);
+          topLevel.addClause(q);
+        }
+      };
+    }
+    
+    /** return the maximum priority queue size */
+    public int getSize() {
+      return delegate.getSize();
+    }
+
+    @Override
+    public SpanQuery rewrite(IndexReader reader, MultiTermQuery query) throws IOException {
+      return delegate.rewrite(reader, query);
+    }
+  
+    @Override
+    public int hashCode() {
+      return 31 * delegate.hashCode();
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+      if (this == obj) return true;
+      if (obj == null) return false;
+      if (getClass() != obj.getClass()) return false;
+      final TopTermsSpanBooleanQueryRewrite other = (TopTermsSpanBooleanQueryRewrite) obj;
+      return delegate.equals(other.delegate);
+    }
+    
+  }
+  
+}

Propchange: lucene/dev/trunk/lucene/src/java/org/apache/lucene/search/spans/SpanMultiTermQueryWrapper.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: lucene/dev/trunk/lucene/src/java/org/apache/lucene/search/spans/SpanMultiTermQueryWrapper.java
------------------------------------------------------------------------------
    svn:keywords = Date Author Id Revision HeadURL

Modified: lucene/dev/trunk/lucene/src/java/org/apache/lucene/search/spans/SpanOrQuery.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/lucene/src/java/org/apache/lucene/search/spans/SpanOrQuery.java?rev=1035096&r1=1035095&r2=1035096&view=diff
==============================================================================
--- lucene/dev/trunk/lucene/src/java/org/apache/lucene/search/spans/SpanOrQuery.java (original)
+++ lucene/dev/trunk/lucene/src/java/org/apache/lucene/search/spans/SpanOrQuery.java Sun Nov 14 23:13:46 2010
@@ -42,16 +42,20 @@ public class SpanOrQuery extends SpanQue
     // copy clauses array into an ArrayList
     this.clauses = new ArrayList<SpanQuery>(clauses.length);
     for (int i = 0; i < clauses.length; i++) {
-      SpanQuery clause = clauses[i];
-      if (i == 0) {                               // check field
-        field = clause.getField();
-      } else if (!clause.getField().equals(field)) {
-        throw new IllegalArgumentException("Clauses must have same field.");
-      }
-      this.clauses.add(clause);
+      addClause(clauses[i]);
     }
   }
 
+  /** Adds a clause to this query */
+  public final void addClause(SpanQuery clause) {
+    if (field == null) {
+      field = clause.getField();
+    } else if (!clause.getField().equals(field)) {
+      throw new IllegalArgumentException("Clauses must have same field.");
+    }
+    this.clauses.add(clause);
+  }
+  
   /** Return the clauses whose spans are matched. */
   public SpanQuery[] getClauses() {
     return clauses.toArray(new SpanQuery[clauses.size()]);

Modified: lucene/dev/trunk/lucene/src/test/org/apache/lucene/search/TestMultiTermQueryRewrites.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/lucene/src/test/org/apache/lucene/search/TestMultiTermQueryRewrites.java?rev=1035096&r1=1035095&r2=1035096&view=diff
==============================================================================
--- lucene/dev/trunk/lucene/src/test/org/apache/lucene/search/TestMultiTermQueryRewrites.java (original)
+++ lucene/dev/trunk/lucene/src/test/org/apache/lucene/search/TestMultiTermQueryRewrites.java Sun Nov 14 23:13:46 2010
@@ -147,8 +147,8 @@ public class TestMultiTermQueryRewrites 
       @Override
       protected TermsEnum getTermsEnum(IndexReader reader, AttributeSource atts) throws IOException {
         return new TermRangeTermsEnum(reader, field, "2", "7", true, true, null) {
-          final MultiTermQuery.BoostAttribute boostAtt =
-            attributes().addAttribute(MultiTermQuery.BoostAttribute.class);
+          final BoostAttribute boostAtt =
+            attributes().addAttribute(BoostAttribute.class);
         
           @Override
           protected AcceptStatus accept(BytesRef term) {

Added: lucene/dev/trunk/lucene/src/test/org/apache/lucene/search/spans/TestSpanMultiTermQueryWrapper.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/lucene/src/test/org/apache/lucene/search/spans/TestSpanMultiTermQueryWrapper.java?rev=1035096&view=auto
==============================================================================
--- lucene/dev/trunk/lucene/src/test/org/apache/lucene/search/spans/TestSpanMultiTermQueryWrapper.java (added)
+++ lucene/dev/trunk/lucene/src/test/org/apache/lucene/search/spans/TestSpanMultiTermQueryWrapper.java Sun Nov 14 23:13:46 2010
@@ -0,0 +1,92 @@
+package org.apache.lucene.search.spans;
+
+/**
+ * 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 org.apache.lucene.document.Document;
+import org.apache.lucene.document.Field;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.RandomIndexWriter;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.search.FuzzyQuery;
+import org.apache.lucene.search.IndexSearcher;
+import org.apache.lucene.search.Searcher;
+import org.apache.lucene.search.WildcardQuery;
+import org.apache.lucene.store.Directory;
+import org.apache.lucene.util.LuceneTestCase;
+
+/**
+ * Tests for {@link SpanMultiTermQueryWrapper}, wrapping a few MultiTermQueries.
+ */
+public class TestSpanMultiTermQueryWrapper extends LuceneTestCase {
+  private Directory directory;
+  private IndexReader reader;
+  private Searcher searcher;
+  
+  @Override
+  public void setUp() throws Exception {
+    super.setUp();
+    directory = newDirectory();
+    RandomIndexWriter iw = new RandomIndexWriter(random, directory);
+    Document doc = new Document();
+    Field field = newField("field", "", Field.Store.NO, Field.Index.ANALYZED);
+    doc.add(field);
+    
+    field.setValue("quick brown fox");
+    iw.addDocument(doc);
+    field.setValue("jumps over lazy broun dog");
+    iw.addDocument(doc);
+    field.setValue("jumps over extremely very lazy broxn dog");
+    iw.addDocument(doc);
+    reader = iw.getReader();
+    iw.close();
+    searcher = new IndexSearcher(reader);
+  }
+  
+  @Override
+  public void tearDown() throws Exception {
+    searcher.close();
+    reader.close();
+    directory.close();
+    super.tearDown();
+  }
+  
+  public void testWildcard() throws Exception {
+    WildcardQuery wq = new WildcardQuery(new Term("field", "bro?n"));
+    SpanQuery swq = new SpanMultiTermQueryWrapper<WildcardQuery>(wq);
+    // will only match quick brown fox
+    SpanFirstQuery sfq = new SpanFirstQuery(swq, 2);
+    assertEquals(1, searcher.search(sfq, 10).totalHits);
+  }
+  
+  public void testFuzzy() throws Exception {
+    FuzzyQuery fq = new FuzzyQuery(new Term("field", "broan"));
+    SpanQuery sfq = new SpanMultiTermQueryWrapper<FuzzyQuery>(fq);
+    // will not match quick brown fox
+    SpanPositionRangeQuery sprq = new SpanPositionRangeQuery(sfq, 3, 6);
+    assertEquals(2, searcher.search(sprq, 10).totalHits);
+  }
+  
+  public void testFuzzy2() throws Exception {
+    // maximum of 1 term expansion
+    FuzzyQuery fq = new FuzzyQuery(new Term("field", "broan"), 1f, 0, 1);
+    SpanQuery sfq = new SpanMultiTermQueryWrapper<FuzzyQuery>(fq);
+    // will only match jumps over lazy broun dog
+    SpanPositionRangeQuery sprq = new SpanPositionRangeQuery(sfq, 0, 100);
+    assertEquals(1, searcher.search(sprq, 10).totalHits);
+  }
+}

Propchange: lucene/dev/trunk/lucene/src/test/org/apache/lucene/search/spans/TestSpanMultiTermQueryWrapper.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: lucene/dev/trunk/lucene/src/test/org/apache/lucene/search/spans/TestSpanMultiTermQueryWrapper.java
------------------------------------------------------------------------------
    svn:keywords = Date Author Id Revision HeadURL