OK, so as I said, my previous version of UnscoredRangeQuery that could work with any number of terms in the range had a problem - it could return duplicates if a doc had more than one term in the range.
Here is how I fixed it: I hacked together an UnscoredQuery that takes a Filter (it's like FilteredQuery without the Query part). My UnscoredRangeQuery now rewrites to an UnscoredQuery wrapping a RangeFilter. Since I have all the docids to start with, the scorer can now implement skipTo. Anyone have a better name than UnscoredQuery? It currently wrapps a filter, but the name FilterQuery seems to close to FilteredQuery. I'll cut-n-paste the classes below since last time at least one person had problems seeing attachments. -Yonik ---------------------- UnscoredRangeQuery.java package org.apache.lucene.search; import java.io.IOException; import org.apache.lucene.index.Term; import org.apache.lucene.index.TermEnum; import org.apache.lucene.index.IndexReader; /** * A Query that can handle any number of terms (unlike the standard RangeQuery * which expands to a boolean query). * A constant score is produced for all documents in the range. * <p> * If an endpoint is null, it is said to be "open". * Either or both endpoints may be open. Open endpoints may not be exclusive * (you can't select all but the first or last term without explicitly specifying them.) * * @author yonik * @version $Id: UnscoredRangeQuery.java,v 1.5 2005/04/18 01:58:14 yonik Exp $ */ public class UnscoredRangeQuery extends Query { private final String fieldName; private final String lowerVal; private final String upperVal; private final boolean includeLower; private final boolean includeUpper; public UnscoredRangeQuery(String fieldName, String lowerVal, String upperVal, boolean includeLower, boolean includeUpper) { // do a little bit of normalization... // open ended range queries should always be inclusive. if (lowerVal==null) { includeLower=true; } else if (includeLower && lowerVal.equals("")) { lowerVal=null; } if (upperVal==null) { includeUpper=true; } this.fieldName = fieldName.intern(); // intern it, just like terms... this.lowerVal = lowerVal; this.upperVal = upperVal; this.includeLower = includeLower; this.includeUpper = includeUpper; } /** Returns the field name for this query */ public String getField() { return fieldName; } /** Returns the value of the lower endpoint of this range query, null if open ended */ public String getLowerVal() { return lowerVal; } /** Returns the value of the upper endpoint of this range query, null if open ended */ public String getUpperVal() { return upperVal; } /** Returns <code>true</code> if the lower endpoint is inclusive */ public boolean includesLower() { return includeLower; } /** Returns <code>true</code> if the upper endpoint is inclusive */ public boolean includesUpper() { return includeUpper; } public Query rewrite(IndexReader reader) throws IOException { // TODO: if number of terms are low enough, rewrite to a BooleanQuery // or RangeQuery.rewrite() for potentially faster execution. // Map to RangeFilter semantics... RangeFilter rangeFilt = new RangeFilter(fieldName, lowerVal!=null?lowerVal:"", upperVal, lowerVal==""?false:includeLower, upperVal==null?false:includeUpper); return new UnscoredQuery(rangeFilt); } /** Prints a user-readable version of this query. */ public String toString(String field) { StringBuffer buffer = new StringBuffer(); if (!getField().equals(field)) { buffer.append(getField()); buffer.append(":"); } buffer.append(includeLower ? '[' : '{'); buffer.append(lowerVal != null ? lowerVal : "*"); buffer.append(" TO "); buffer.append(upperVal != null ? upperVal : "*"); buffer.append(includeUpper ? ']' : '}'); if (getBoost() != 1.0f) { buffer.append("^"); buffer.append(Float.toString(getBoost())); } return buffer.toString(); } /** Returns true if <code>o</code> is equal to this. */ public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof UnscoredRangeQuery)) return false; UnscoredRangeQuery other = (UnscoredRangeQuery) o; if (this.fieldName != other.fieldName // interned comparison || this.includeLower != other.includeLower || this.includeUpper != other.includeUpper ) { return false; } if (this.lowerVal != null ? !this.lowerVal.equals(other.upperVal) : other.lowerVal != null) return false; if (this.upperVal != null ? !this.upperVal.equals(other.upperVal) : other.upperVal != null) return false; return this.getBoost() == other.getBoost(); } /** Returns a hash code value for this object.*/ public int hashCode() { int h = Float.floatToIntBits(getBoost()) ^ fieldName.hashCode(); // hashCode of "" is 0, so don't use that for null... h ^= lowerVal != null ? lowerVal.hashCode() : 0x965a965a; // don't just XOR upperVal with out mixing either it or h, as it will cancel // out lowerVal if they are equal. h ^= (h << 17) | (h >>> 16); // a reversible (one to one) 32 bit mapping mix h ^= (upperVal != null ? (upperVal.hashCode()) : 0x5a695a69); h ^= (includeLower ? 0x665599aa : 0x5566aa99) ^ (includeUpper ? 0x99aa5566 : 0xaa996655); return h; } } ------------------ UnscoredQuery.java package org.apache.lucene.search; import org.apache.lucene.index.IndexReader; import java.io.IOException; import java.util.BitSet; /** * @author yonik * @version $Id: UnscoredQuery.java,v 1.1 2005/04/18 01:53:13 yonik Exp $ */ public class UnscoredQuery extends Query { protected final Filter filter; public UnscoredQuery(Filter filter) { this.filter=filter; } public Query rewrite(IndexReader reader) throws IOException { return this; } protected class UnscoredWeight implements Weight { private Searcher searcher; private float queryNorm; public UnscoredWeight(Searcher searcher) { this.searcher = searcher; } public Query getQuery() { return UnscoredQuery.this; } public float getValue() { return 1e-4f; } public float sumOfSquaredWeights() throws IOException { return 1e-4f; } public void normalize(float norm) { this.queryNorm = norm; } public Scorer scorer(IndexReader reader) throws IOException { return new UnscoredScorer(getSimilarity(searcher), reader, this); } public Explanation explain(IndexReader reader, int doc) throws IOException { return new Explanation(); // TODO } } protected class UnscoredScorer extends Scorer { protected final BitSet bits; int doc=-1; public UnscoredScorer(Similarity similarity, IndexReader reader, Weight w) throws IOException { super(similarity); bits = filter.bits(reader); } public boolean next() throws IOException { doc = bits.nextSetBit(doc+1); return doc >= 0; } public int doc() { return doc; } public float score() throws IOException { return 1e-4f; } public boolean skipTo(int target) throws IOException { doc = bits.nextSetBit(target); return doc >= 0; } public Explanation explain(int doc) throws IOException { return new Explanation(); //TODO } } protected Weight createWeight(Searcher searcher) { return new UnscoredQuery.UnscoredWeight(searcher); } /** Prints a user-readable version of this query. */ public String toString(String field) { return filter.toString(); } /** Returns true if <code>o</code> is equal to this. */ public boolean equals(Object o) { if (true) throw new UnsupportedOperationException("Filters don't support equals yet!"); if (this == o) return true; if (!(o instanceof UnscoredQuery)) return false; UnscoredQuery other = (UnscoredQuery)o; return this.getBoost()==other.getBoost() && filter.equals(other.filter); } /** Returns a hash code value for this object.*/ public int hashCode() { if (true) throw new UnsupportedOperationException("Filters don't support hashCode yet!"); int h = Float.floatToIntBits(getBoost()); int h2 = filter.hashCode(); h ^= ((h2 << 8) | (h2 >>> 25)); return h; } } --------------------------------------------------------------------- To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]