Index: src/java/org/apache/lucene/search/FieldCacheRangeFilter.java =================================================================== --- src/java/org/apache/lucene/search/FieldCacheRangeFilter.java (revision 788597) +++ src/java/org/apache/lucene/search/FieldCacheRangeFilter.java (working copy) @@ -19,74 +19,460 @@ import java.io.IOException; import org.apache.lucene.index.IndexReader; +import org.apache.lucene.index.TermDocs; +import org.apache.lucene.util.NumericUtils; +import org.apache.lucene.document.NumericField; // for javadocs /** - * A range filter built on top of a cached single term field (in FieldCache). + * A range filter built on top of a cached single term field (in {@link FieldCache}). * - * FieldCacheRangeFilter builds a single cache for the field the first time it is used. - * + *

FieldCacheRangeFilter builds a single cache for the field the first time it is used. * Each subsequent FieldCacheRangeFilter on the same field then reuses this cache, * even if the range itself changes. * - * This means that FieldCacheRangeFilter is much faster (sometimes more than 100x as fast) - * as building a RangeFilter (or ConstantScoreRangeQuery on a RangeFilter) for each query. - * However, if the range never changes it is slower (around 2x as slow) than building a - * CachingWrapperFilter on top of a single RangeFilter. - * - * As with all FieldCache based functionality, FieldCacheRangeFilter is only valid for - * fields which contain zero or one terms for each document. Thus it works on dates, - * prices and other single value fields but will not work on regular text fields. It is - * preferable to use an UN_TOKENIZED field to ensure that there is only a single term. + *

This means that FieldCacheRangeFilter is much faster (sometimes more than 100x as fast) + * as building a {@link RangeFilter} (or {@link ConstantScoreRangeQuery} on a {@link RangeFilter}) + * for each query, if using a {@link #newStringRange}. However, if the range never changes it + * is slower (around 2x as slow) than building a CachingWrapperFilter on top of a single RangeFilter. * - * Also, collation is done at the time the FieldCache is built; to change - * collation you need to override the getFieldCache() method to change the underlying cache. + * For numeric data types, this filter may be significantly faster than {@link NumericRangeFilter}. + * Furthermore, it does not need the numeric values encoded by {@link NumericField}. But + * it has the problem that it only works with exact one value/document (see below). + * + *

As with all {@link FieldCache} based functionality, FieldCacheRangeFilter is only valid for + * fields which exact one term for each document (except for {@link #newStringRange} + * where 0 terms are also allowed). Due to a restriction of {@link FieldCache}, for numeric ranges + * all terms that do not have a numeric value, 0 is assumed. + * + *

Thus it works on dates, prices and other single value fields but will not work on + * regular text fields. It is preferable to use a NOT_ANALYZED field to ensure that + * there is only a single term. + * + *

This class does not have an constructor, use one of the static factory methods available, + * that create a correct instance for different data types supported by {@link FieldCache}. */ -public class FieldCacheRangeFilter extends Filter { - private String field; - private String lowerVal; - private String upperVal; - private boolean includeLower; - private boolean includeUpper; +public abstract class FieldCacheRangeFilter extends Filter { + final String field; + final FieldCache.Parser parser; + final Object lowerVal; + final Object upperVal; + final boolean includeLower; + final boolean includeUpper; - public FieldCacheRangeFilter( - String field, - String lowerVal, - String upperVal, - boolean includeLower, - boolean includeUpper) { + private FieldCacheRangeFilter(String field, FieldCache.Parser parser, Object lowerVal, Object upperVal, boolean includeLower, boolean includeUpper) { + if (lowerVal == null && upperVal == null) + throw new IllegalArgumentException("At least one value must be non-null"); this.field = field; + this.parser = parser; this.lowerVal = lowerVal; this.upperVal = upperVal; this.includeLower = includeLower; this.includeUpper = includeUpper; } + + /** This method is implemented for each data type */ + abstract DocIdSetIterator newDocIdSetIterator(IndexReader reader) throws IOException; - public FieldCache getFieldCache() { - return FieldCache.DEFAULT; + /** + * Creates a string range query using {@link FieldCache#getStringIndex}. This works with all + * fields containing zero or one term in the field. The range can be half-open by setting one + * of the values to null. + */ + public static FieldCacheRangeFilter newStringRange(String field, String lowerVal, String upperVal, boolean includeLower, boolean includeUpper) { + return new FieldCacheRangeFilter(field, null, lowerVal, upperVal, includeLower, includeUpper) { + DocIdSetIterator newDocIdSetIterator(IndexReader reader) throws IOException { + final FieldCache.StringIndex fcsi = FieldCache.DEFAULT.getStringIndex(reader, field); + int lowerPoint = fcsi.binarySearchLookup((String) lowerVal); + int upperPoint = fcsi.binarySearchLookup((String) upperVal); + + // hint: binarySearchLookup returns 0, if value was null + + final int inclusiveLowerPoint, inclusiveUpperPoint; + if (lowerPoint == 0) { + assert lowerVal == null; + inclusiveLowerPoint = 1; + } else { + inclusiveLowerPoint = includeLower ? lowerPoint : Math.min(fcsi.lookup.length-1, lowerPoint + 1); + } + if (upperPoint == 0) { + assert upperVal == null; + inclusiveUpperPoint = Integer.MAX_VALUE; + } else { + // enforce bound > 1 + inclusiveUpperPoint = includeUpper ? upperPoint : Math.max(1, upperPoint - 1); + } + + if (inclusiveLowerPoint > inclusiveUpperPoint) + return DocIdSet.EMPTY_DOCIDSET.iterator(); + + // for this iterator, we do not need to use TermDocs, + // because deleted docs have an order of 0 (null entry in StringIndex) + return new DocIdSetIterator() { + private int doc = -1; + + /** @deprecated use {@link #nextDoc()} instead. */ + public boolean next() throws IOException { + return nextDoc() != NO_MORE_DOCS; + } + + /** @deprecated use {@link #advance(int)} instead. */ + public boolean skipTo(int target) throws IOException { + return advance(target) != NO_MORE_DOCS; + } + + /** @deprecated use {@link #docID()} instead. */ + public int doc() { + return doc; + } + + public int docID() { + return doc; + } + + public int nextDoc() { + try { + do { + doc++; + } while (fcsi.order[doc] > inclusiveUpperPoint || fcsi.order[doc] < inclusiveLowerPoint); + return doc; + } catch (ArrayIndexOutOfBoundsException e) { + return doc = NO_MORE_DOCS; + } + } + + public int advance(int target) { + try { + doc = target; + while (fcsi.order[doc] > inclusiveUpperPoint || fcsi.order[doc] < inclusiveLowerPoint) { + doc++; + } + return doc; + } catch (ArrayIndexOutOfBoundsException e) { + return doc = NO_MORE_DOCS; + } + } + + }; + } + }; } - public DocIdSet getDocIdSet(IndexReader reader) throws IOException { - return new RangeMultiFilterDocIdSet(getFieldCache().getStringIndex(reader, field)); + /** + * Creates a numeric range query using {@link FieldCache#getBytes(IndexReader,String)}. This works with all + * byte fields containing exactly one numeric term in the field. The range can be half-open by setting one + * of the values to null. + */ + public static FieldCacheRangeFilter newByteRange(String field, Byte lowerVal, Byte upperVal, boolean includeLower, boolean includeUpper) { + return newByteRange(field, null, lowerVal, upperVal, includeLower, includeUpper); } - public String toString() { - StringBuffer buffer = new StringBuffer(); - buffer.append(field); - buffer.append(":"); - buffer.append(includeLower ? "[" : "{"); - if (null != lowerVal) { - buffer.append(lowerVal); - } - buffer.append("-"); - if (null != upperVal) { - buffer.append(upperVal); - } - buffer.append(includeUpper ? "]" : "}"); - return buffer.toString(); + /** + * Creates a numeric range query using {@link FieldCache#getBytes(IndexReader,String,FieldCache.ByteParser)}. This works with all + * byte fields containing exactly one numeric term in the field. The range can be half-open by setting one + * of the values to null. + */ + public static FieldCacheRangeFilter newByteRange(String field, FieldCache.ByteParser parser, Byte lowerVal, Byte upperVal, boolean includeLower, boolean includeUpper) { + return new FieldCacheRangeFilter(field, parser, lowerVal, upperVal, includeLower, includeUpper) { + DocIdSetIterator newDocIdSetIterator(IndexReader reader) throws IOException { + final byte inclusiveLowerPoint, inclusiveUpperPoint; + if (lowerVal != null) { + final byte i = ((Number) lowerVal).byteValue(); + if (!includeLower && i == Byte.MAX_VALUE) + return DocIdSet.EMPTY_DOCIDSET.iterator(); + inclusiveLowerPoint = (byte) (includeLower ? i : (i + 1)); + } else { + inclusiveLowerPoint = Byte.MIN_VALUE; + } + if (upperVal != null) { + final byte i = ((Number) upperVal).byteValue(); + if (!includeUpper && i == Byte.MIN_VALUE) + return DocIdSet.EMPTY_DOCIDSET.iterator(); + inclusiveUpperPoint = (byte) (includeUpper ? i : (i - 1)); + } else { + inclusiveUpperPoint = Byte.MAX_VALUE; + } + + if (inclusiveLowerPoint > inclusiveUpperPoint) + return DocIdSet.EMPTY_DOCIDSET.iterator(); + + final byte[] values = FieldCache.DEFAULT.getBytes(reader, field, (FieldCache.ByteParser) parser); + return new FilteredTermDocsIterator(reader) { + boolean matchDoc(int doc) { + return values[doc] >= inclusiveLowerPoint && values[doc] <= inclusiveUpperPoint; + } + }; + } + }; } + + /** + * Creates a numeric range query using {@link FieldCache#getShorts(IndexReader,String)}. This works with all + * short fields containing exactly one numeric term in the field. The range can be half-open by setting one + * of the values to null. + */ + public static FieldCacheRangeFilter newShortRange(String field, Short lowerVal, Short upperVal, boolean includeLower, boolean includeUpper) { + return newShortRange(field, null, lowerVal, upperVal, includeLower, includeUpper); + } + + /** + * Creates a numeric range query using {@link FieldCache#getShorts(IndexReader,String,FieldCache.ShortParser)}. This works with all + * short fields containing exactly one numeric term in the field. The range can be half-open by setting one + * of the values to null. + */ + public static FieldCacheRangeFilter newShortRange(String field, FieldCache.ShortParser parser, Short lowerVal, Short upperVal, boolean includeLower, boolean includeUpper) { + return new FieldCacheRangeFilter(field, parser, lowerVal, upperVal, includeLower, includeUpper) { + DocIdSetIterator newDocIdSetIterator(IndexReader reader) throws IOException { + final short inclusiveLowerPoint, inclusiveUpperPoint; + if (lowerVal != null) { + short i = ((Number) lowerVal).shortValue(); + if (!includeLower && i == Short.MAX_VALUE) + return DocIdSet.EMPTY_DOCIDSET.iterator(); + inclusiveLowerPoint = (short) (includeLower ? i : (i + 1)); + } else { + inclusiveLowerPoint = Short.MIN_VALUE; + } + if (upperVal != null) { + short i = ((Number) upperVal).shortValue(); + if (!includeUpper && i == Short.MIN_VALUE) + return DocIdSet.EMPTY_DOCIDSET.iterator(); + inclusiveUpperPoint = (short) (includeUpper ? i : (i - 1)); + } else { + inclusiveUpperPoint = Short.MAX_VALUE; + } + + if (inclusiveLowerPoint > inclusiveUpperPoint) + return DocIdSet.EMPTY_DOCIDSET.iterator(); + + final short[] values = FieldCache.DEFAULT.getShorts(reader, field, (FieldCache.ShortParser) parser); + return new FilteredTermDocsIterator(reader) { + boolean matchDoc(int doc) { + return values[doc] >= inclusiveLowerPoint && values[doc] <= inclusiveUpperPoint; + } + }; + } + }; + } + + /** + * Creates a numeric range query using {@link FieldCache#getInts(IndexReader,String)}. This works with all + * int fields containing exactly one numeric term in the field. The range can be half-open by setting one + * of the values to null. + */ + public static FieldCacheRangeFilter newIntRange(String field, Integer lowerVal, Integer upperVal, boolean includeLower, boolean includeUpper) { + return newIntRange(field, null, lowerVal, upperVal, includeLower, includeUpper); + } + + /** + * Creates a numeric range query using {@link FieldCache#getInts(IndexReader,String,FieldCache.IntParser)}. This works with all + * int fields containing exactly one numeric term in the field. The range can be half-open by setting one + * of the values to null. + */ + public static FieldCacheRangeFilter newIntRange(String field, FieldCache.IntParser parser, Integer lowerVal, Integer upperVal, boolean includeLower, boolean includeUpper) { + return new FieldCacheRangeFilter(field, parser, lowerVal, upperVal, includeLower, includeUpper) { + DocIdSetIterator newDocIdSetIterator(IndexReader reader) throws IOException { + final int inclusiveLowerPoint, inclusiveUpperPoint; + if (lowerVal != null) { + int i = ((Number) lowerVal).intValue(); + if (!includeLower && i == Integer.MAX_VALUE) + return DocIdSet.EMPTY_DOCIDSET.iterator(); + inclusiveLowerPoint = includeLower ? i : (i + 1); + } else { + inclusiveLowerPoint = Integer.MIN_VALUE; + } + if (upperVal != null) { + int i = ((Number) upperVal).intValue(); + if (!includeUpper && i == Integer.MIN_VALUE) + return DocIdSet.EMPTY_DOCIDSET.iterator(); + inclusiveUpperPoint = includeUpper ? i : (i - 1); + } else { + inclusiveUpperPoint = Integer.MAX_VALUE; + } + + if (inclusiveLowerPoint > inclusiveUpperPoint) + return DocIdSet.EMPTY_DOCIDSET.iterator(); + + final int[] values = FieldCache.DEFAULT.getInts(reader, field, (FieldCache.IntParser) parser); + return new FilteredTermDocsIterator(reader) { + boolean matchDoc(int doc) { + return values[doc] >= inclusiveLowerPoint && values[doc] <= inclusiveUpperPoint; + } + }; + } + }; + } + + /** + * Creates a numeric range query using {@link FieldCache#getLongs(IndexReader,String)}. This works with all + * long fields containing exactly one numeric term in the field. The range can be half-open by setting one + * of the values to null. + */ + public static FieldCacheRangeFilter newLongRange(String field, Long lowerVal, Long upperVal, boolean includeLower, boolean includeUpper) { + return newLongRange(field, null, lowerVal, upperVal, includeLower, includeUpper); + } + + /** + * Creates a numeric range query using {@link FieldCache#getLongs(IndexReader,String,FieldCache.LongParser)}. This works with all + * long fields containing exactly one numeric term in the field. The range can be half-open by setting one + * of the values to null. + */ + public static FieldCacheRangeFilter newLongRange(String field, FieldCache.LongParser parser, Long lowerVal, Long upperVal, boolean includeLower, boolean includeUpper) { + return new FieldCacheRangeFilter(field, parser, lowerVal, upperVal, includeLower, includeUpper) { + DocIdSetIterator newDocIdSetIterator(IndexReader reader) throws IOException { + final long inclusiveLowerPoint, inclusiveUpperPoint; + if (lowerVal != null) { + long i = ((Number) lowerVal).longValue(); + if (!includeLower && i == Long.MAX_VALUE) + return DocIdSet.EMPTY_DOCIDSET.iterator(); + inclusiveLowerPoint = includeLower ? i : (i + 1L); + } else { + inclusiveLowerPoint = Long.MIN_VALUE; + } + if (upperVal != null) { + long i = ((Number) upperVal).longValue(); + if (!includeUpper && i == Long.MIN_VALUE) + return DocIdSet.EMPTY_DOCIDSET.iterator(); + inclusiveUpperPoint = includeUpper ? i : (i - 1L); + } else { + inclusiveUpperPoint = Long.MAX_VALUE; + } + + if (inclusiveLowerPoint > inclusiveUpperPoint) + return DocIdSet.EMPTY_DOCIDSET.iterator(); + + final long[] values = FieldCache.DEFAULT.getLongs(reader, field, (FieldCache.LongParser) parser); + return new FilteredTermDocsIterator(reader) { + boolean matchDoc(int doc) { + return values[doc] >= inclusiveLowerPoint && values[doc] <= inclusiveUpperPoint; + } + }; + } + }; + } + + /** + * Creates a numeric range query using {@link FieldCache#getFloats(IndexReader,String)}. This works with all + * float fields containing exactly one numeric term in the field. The range can be half-open by setting one + * of the values to null. + */ + public static FieldCacheRangeFilter newFloatRange(String field, Float lowerVal, Float upperVal, boolean includeLower, boolean includeUpper) { + return newFloatRange(field, null, lowerVal, upperVal, includeLower, includeUpper); + } + + /** + * Creates a numeric range query using {@link FieldCache#getFloats(IndexReader,String,FieldCache.FloatParser)}. This works with all + * float fields containing exactly one numeric term in the field. The range can be half-open by setting one + * of the values to null. + */ + public static FieldCacheRangeFilter newFloatRange(String field, FieldCache.FloatParser parser, Float lowerVal, Float upperVal, boolean includeLower, boolean includeUpper) { + return new FieldCacheRangeFilter(field, parser, lowerVal, upperVal, includeLower, includeUpper) { + DocIdSetIterator newDocIdSetIterator(IndexReader reader) throws IOException { + // we transform the floating point numbers to sortable integers + // using NumericUtils to easier find the next bigger/lower value + final float inclusiveLowerPoint, inclusiveUpperPoint; + if (lowerVal != null) { + float f = ((Number) lowerVal).floatValue(); + if (!includeUpper && f > 0.0f && Float.isInfinite(f)) + return DocIdSet.EMPTY_DOCIDSET.iterator(); + int i = NumericUtils.floatToSortableInt(f); + inclusiveLowerPoint = NumericUtils.sortableIntToFloat( includeLower ? i : (i + 1) ); + } else { + inclusiveLowerPoint = Float.NEGATIVE_INFINITY; + } + if (upperVal != null) { + float f = ((Number) upperVal).floatValue(); + if (!includeUpper && f < 0.0f && Float.isInfinite(f)) + return DocIdSet.EMPTY_DOCIDSET.iterator(); + int i = NumericUtils.floatToSortableInt(f); + inclusiveUpperPoint = NumericUtils.sortableIntToFloat( includeUpper ? i : (i - 1) ); + } else { + inclusiveUpperPoint = Float.POSITIVE_INFINITY; + } + + if (inclusiveLowerPoint > inclusiveUpperPoint) + return DocIdSet.EMPTY_DOCIDSET.iterator(); + + final float[] values = FieldCache.DEFAULT.getFloats(reader, field, (FieldCache.FloatParser) parser); + return new FilteredTermDocsIterator(reader) { + boolean matchDoc(int doc) { + return values[doc] >= inclusiveLowerPoint && values[doc] <= inclusiveUpperPoint; + } + }; + } + }; + } + + /** + * Creates a numeric range query using {@link FieldCache#getDoubles(IndexReader,String)}. This works with all + * double fields containing exactly one numeric term in the field. The range can be half-open by setting one + * of the values to null. + */ + public static FieldCacheRangeFilter newDoubleRange(String field, Double lowerVal, Double upperVal, boolean includeLower, boolean includeUpper) { + return newDoubleRange(field, null, lowerVal, upperVal, includeLower, includeUpper); + } + + /** + * Creates a numeric range query using {@link FieldCache#getDoubles(IndexReader,String,FieldCache.DoubleParser)}. This works with all + * double fields containing exactly one numeric term in the field. The range can be half-open by setting one + * of the values to null. + */ + public static FieldCacheRangeFilter newDoubleRange(String field, FieldCache.DoubleParser parser, Double lowerVal, Double upperVal, boolean includeLower, boolean includeUpper) { + return new FieldCacheRangeFilter(field, parser, lowerVal, upperVal, includeLower, includeUpper) { + DocIdSetIterator newDocIdSetIterator(IndexReader reader) throws IOException { + // we transform the floating point numbers to sortable integers + // using NumericUtils to easier find the next bigger/lower value + final double inclusiveLowerPoint, inclusiveUpperPoint; + if (lowerVal != null) { + double f = ((Number) lowerVal).doubleValue(); + if (!includeUpper && f > 0.0 && Double.isInfinite(f)) + return DocIdSet.EMPTY_DOCIDSET.iterator(); + long i = NumericUtils.doubleToSortableLong(f); + inclusiveLowerPoint = NumericUtils.sortableLongToDouble( includeLower ? i : (i + 1L) ); + } else { + inclusiveLowerPoint = Double.NEGATIVE_INFINITY; + } + if (upperVal != null) { + double f = ((Number) upperVal).doubleValue(); + if (!includeUpper && f < 0.0 && Double.isInfinite(f)) + return DocIdSet.EMPTY_DOCIDSET.iterator(); + long i = NumericUtils.doubleToSortableLong(f); + inclusiveUpperPoint = NumericUtils.sortableLongToDouble( includeUpper ? i : (i - 1L) ); + } else { + inclusiveUpperPoint = Double.POSITIVE_INFINITY; + } + + if (inclusiveLowerPoint > inclusiveUpperPoint) + return DocIdSet.EMPTY_DOCIDSET.iterator(); + + final double[] values = FieldCache.DEFAULT.getDoubles(reader, field, (FieldCache.DoubleParser) parser); + return new FilteredTermDocsIterator(reader) { + boolean matchDoc(int doc) { + return values[doc] >= inclusiveLowerPoint && values[doc] <= inclusiveUpperPoint; + } + }; + } + }; + } + + public final DocIdSet getDocIdSet(final IndexReader reader) throws IOException { + return new DocIdSet() { + public DocIdSetIterator iterator() throws IOException { + return newDocIdSetIterator(reader); + } + }; + } + + public final String toString() { + final StringBuffer sb = new StringBuffer(field).append(":"); + return sb.append(includeLower ? '[' : '{') + .append((lowerVal == null) ? "*" : lowerVal.toString()) + .append(" TO ") + .append((upperVal == null) ? "*" : upperVal.toString()) + .append(includeUpper ? ']' : '}') + .toString(); + } - public boolean equals(Object o) { + public final boolean equals(Object o) { if (this == o) return true; if (!(o instanceof FieldCacheRangeFilter)) return false; FieldCacheRangeFilter other = (FieldCacheRangeFilter) o; @@ -97,112 +483,69 @@ ) { return false; } if (this.lowerVal != null ? !this.lowerVal.equals(other.lowerVal) : other.lowerVal != null) return false; if (this.upperVal != null ? !this.upperVal.equals(other.upperVal) : other.upperVal != null) return false; + if (this.parser != null ? !this.parser.equals(other.parser) : other.parser != null) return false; return true; } - public int hashCode() { + public final int hashCode() { int h = field.hashCode(); - h ^= lowerVal != null ? lowerVal.hashCode() : 550356204; + h ^= (lowerVal != null) ? lowerVal.hashCode() : 550356204; h = (h << 1) | (h >>> 31); // rotate to distinguish lower from upper - h ^= (upperVal != null ? (upperVal.hashCode()) : -1674416163); - h ^= (includeLower ? 1549299360 : -365038026) - ^ (includeUpper ? 1721088258 : 1948649653); - + h ^= (upperVal != null) ? upperVal.hashCode() : -1674416163; + h ^= (parser != null) ? parser.hashCode() : -1572457324; + h ^= (includeLower ? 1549299360 : -365038026) ^ (includeUpper ? 1721088258 : 1948649653); return h; } - - protected class RangeMultiFilterDocIdSet extends DocIdSet { - private int inclusiveLowerPoint; - private int inclusiveUpperPoint; - private FieldCache.StringIndex fcsi; + + /** a helper class, that iterates over all TermDocs of a reader, + * filtering doc ids */ + static abstract class FilteredTermDocsIterator extends DocIdSetIterator { + private final TermDocs termDocs; + private int doc = -1; - public RangeMultiFilterDocIdSet(FieldCache.StringIndex fcsi) { - this.fcsi = fcsi; - initialize(); + FilteredTermDocsIterator(IndexReader reader) throws IOException { + termDocs = reader.termDocs(null); } - private void initialize() { - int lowerPoint = fcsi.binarySearchLookup(lowerVal); - int upperPoint = fcsi.binarySearchLookup(upperVal); + /** this method checks, if a doc is a hit */ + abstract boolean matchDoc(int doc); - if (lowerPoint == 0 && upperPoint == 0) { - throw new IllegalArgumentException("At least one value must be non-null"); - } - - if (includeLower && lowerPoint == 0) { - throw new IllegalArgumentException("The lower bound must be non-null to be inclusive"); - } else if (includeLower && lowerPoint > 0) { - inclusiveLowerPoint = lowerPoint; - } else if (lowerPoint >= 0) { - inclusiveLowerPoint = lowerPoint+1; - } else { - inclusiveLowerPoint = -lowerPoint-1; - } - - if (includeUpper && upperPoint == 0) { - throw new IllegalArgumentException("The upper bound must be non-null to be inclusive"); - } else if (upperPoint == 0) { - inclusiveUpperPoint = Integer.MAX_VALUE; - } else if (includeUpper && upperPoint > 0) { - inclusiveUpperPoint = upperPoint; - } else if (upperPoint >= 0) { - inclusiveUpperPoint = upperPoint - 1; - } else { - inclusiveUpperPoint = -upperPoint - 2; - } + /** @deprecated use {@link #nextDoc()} instead. */ + public boolean next() throws IOException { + return nextDoc() != NO_MORE_DOCS; } - public DocIdSetIterator iterator() { - return new RangeMultiFilterIterator(); + /** @deprecated use {@link #advance(int)} instead. */ + public boolean skipTo(int target) throws IOException { + return advance(target) != NO_MORE_DOCS; } + + /** @deprecated use {@link #docID()} instead. */ + public int doc() { + return termDocs.doc(); + } - protected class RangeMultiFilterIterator extends DocIdSetIterator { - private int doc = -1; - - /** @deprecated use {@link #docID()} instead. */ - public int doc() { - return doc; + public int docID() { + return doc; + } + + public int nextDoc() throws IOException { + do { + if (!termDocs.next()) + return doc = NO_MORE_DOCS; + } while (!matchDoc(doc = termDocs.doc())); + return doc; + } + + public int advance(int target) throws IOException { + if (!termDocs.skipTo(target)) + return doc = NO_MORE_DOCS; + while (!matchDoc(doc = termDocs.doc())) { + if (!termDocs.next()) + return doc = NO_MORE_DOCS; } - - public int docID() { - return doc; - } - - /** @deprecated use {@link #nextDoc()} instead. */ - public boolean next() { - return nextDoc() != NO_MORE_DOCS; - } - - public int nextDoc() { - try { - do { - doc++; - } while (fcsi.order[doc] > inclusiveUpperPoint - || fcsi.order[doc] < inclusiveLowerPoint); - } catch (ArrayIndexOutOfBoundsException e) { - doc = NO_MORE_DOCS; - } - return doc; - } - - /** @deprecated use {@link #advance(int)} instead. */ - public boolean skipTo(int target) { - return advance(target) != NO_MORE_DOCS; - } - - public int advance(int target) { - try { - doc = target; - while (fcsi.order[doc] > inclusiveUpperPoint - || fcsi.order[doc] < inclusiveLowerPoint) { - doc++; - } - } catch (ArrayIndexOutOfBoundsException e) { - doc = NO_MORE_DOCS; - } - return doc; - } - + return doc; } } + } Index: src/test/org/apache/lucene/search/TestFieldCacheRangeFilter.java =================================================================== --- src/test/org/apache/lucene/search/TestFieldCacheRangeFilter.java (revision 788597) +++ src/test/org/apache/lucene/search/TestFieldCacheRangeFilter.java (working copy) @@ -67,64 +67,64 @@ // test id, bounded on both ends - result = search.search(q,new FieldCacheRangeFilter("id",minIP,maxIP,T,T), numDocs).scoreDocs; + result = search.search(q,FieldCacheRangeFilter.newStringRange("id",minIP,maxIP,T,T), numDocs).scoreDocs; assertEquals("find all", numDocs, result.length); - result = search.search(q,new FieldCacheRangeFilter("id",minIP,maxIP,T,F), numDocs).scoreDocs; + result = search.search(q,FieldCacheRangeFilter.newStringRange("id",minIP,maxIP,T,F), numDocs).scoreDocs; assertEquals("all but last", numDocs-1, result.length); - result = search.search(q,new FieldCacheRangeFilter("id",minIP,maxIP,F,T), numDocs).scoreDocs; + result = search.search(q,FieldCacheRangeFilter.newStringRange("id",minIP,maxIP,F,T), numDocs).scoreDocs; assertEquals("all but first", numDocs-1, result.length); - result = search.search(q,new FieldCacheRangeFilter("id",minIP,maxIP,F,F), numDocs).scoreDocs; + result = search.search(q,FieldCacheRangeFilter.newStringRange("id",minIP,maxIP,F,F), numDocs).scoreDocs; assertEquals("all but ends", numDocs-2, result.length); - result = search.search(q,new FieldCacheRangeFilter("id",medIP,maxIP,T,T), numDocs).scoreDocs; + result = search.search(q,FieldCacheRangeFilter.newStringRange("id",medIP,maxIP,T,T), numDocs).scoreDocs; assertEquals("med and up", 1+ maxId-medId, result.length); - result = search.search(q,new FieldCacheRangeFilter("id",minIP,medIP,T,T), numDocs).scoreDocs; + result = search.search(q,FieldCacheRangeFilter.newStringRange("id",minIP,medIP,T,T), numDocs).scoreDocs; assertEquals("up to med", 1+ medId-minId, result.length); // unbounded id - result = search.search(q,new FieldCacheRangeFilter("id",minIP,null,T,F), numDocs).scoreDocs; + result = search.search(q,FieldCacheRangeFilter.newStringRange("id",minIP,null,T,F), numDocs).scoreDocs; assertEquals("min and up", numDocs, result.length); - result = search.search(q,new FieldCacheRangeFilter("id",null,maxIP,F,T), numDocs).scoreDocs; + result = search.search(q,FieldCacheRangeFilter.newStringRange("id",null,maxIP,F,T), numDocs).scoreDocs; assertEquals("max and down", numDocs, result.length); - result = search.search(q,new FieldCacheRangeFilter("id",minIP,null,F,F), numDocs).scoreDocs; + result = search.search(q,FieldCacheRangeFilter.newStringRange("id",minIP,null,F,F), numDocs).scoreDocs; assertEquals("not min, but up", numDocs-1, result.length); - result = search.search(q,new FieldCacheRangeFilter("id",null,maxIP,F,F), numDocs).scoreDocs; + result = search.search(q,FieldCacheRangeFilter.newStringRange("id",null,maxIP,F,F), numDocs).scoreDocs; assertEquals("not max, but down", numDocs-1, result.length); - result = search.search(q,new FieldCacheRangeFilter("id",medIP,maxIP,T,F), numDocs).scoreDocs; + result = search.search(q,FieldCacheRangeFilter.newStringRange("id",medIP,maxIP,T,F), numDocs).scoreDocs; assertEquals("med and up, not max", maxId-medId, result.length); - result = search.search(q,new FieldCacheRangeFilter("id",minIP,medIP,F,T), numDocs).scoreDocs; + result = search.search(q,FieldCacheRangeFilter.newStringRange("id",minIP,medIP,F,T), numDocs).scoreDocs; assertEquals("not min, up to med", medId-minId, result.length); // very small sets - result = search.search(q,new FieldCacheRangeFilter("id",minIP,minIP,F,F), numDocs).scoreDocs; + result = search.search(q,FieldCacheRangeFilter.newStringRange("id",minIP,minIP,F,F), numDocs).scoreDocs; assertEquals("min,min,F,F", 0, result.length); - result = search.search(q,new FieldCacheRangeFilter("id",medIP,medIP,F,F), numDocs).scoreDocs; + result = search.search(q,FieldCacheRangeFilter.newStringRange("id",medIP,medIP,F,F), numDocs).scoreDocs; assertEquals("med,med,F,F", 0, result.length); - result = search.search(q,new FieldCacheRangeFilter("id",maxIP,maxIP,F,F), numDocs).scoreDocs; + result = search.search(q,FieldCacheRangeFilter.newStringRange("id",maxIP,maxIP,F,F), numDocs).scoreDocs; assertEquals("max,max,F,F", 0, result.length); - result = search.search(q,new FieldCacheRangeFilter("id",minIP,minIP,T,T), numDocs).scoreDocs; + result = search.search(q,FieldCacheRangeFilter.newStringRange("id",minIP,minIP,T,T), numDocs).scoreDocs; assertEquals("min,min,T,T", 1, result.length); - result = search.search(q,new FieldCacheRangeFilter("id",null,minIP,F,T), numDocs).scoreDocs; + result = search.search(q,FieldCacheRangeFilter.newStringRange("id",null,minIP,F,T), numDocs).scoreDocs; assertEquals("nul,min,F,T", 1, result.length); - result = search.search(q,new FieldCacheRangeFilter("id",maxIP,maxIP,T,T), numDocs).scoreDocs; + result = search.search(q,FieldCacheRangeFilter.newStringRange("id",maxIP,maxIP,T,T), numDocs).scoreDocs; assertEquals("max,max,T,T", 1, result.length); - result = search.search(q,new FieldCacheRangeFilter("id",maxIP,null,T,F), numDocs).scoreDocs; + result = search.search(q,FieldCacheRangeFilter.newStringRange("id",maxIP,null,T,F), numDocs).scoreDocs; assertEquals("max,nul,T,T", 1, result.length); - result = search.search(q,new FieldCacheRangeFilter("id",medIP,medIP,T,T), numDocs).scoreDocs; + result = search.search(q,FieldCacheRangeFilter.newStringRange("id",medIP,medIP,T,T), numDocs).scoreDocs; assertEquals("med,med,T,T", 1, result.length); } @@ -146,47 +146,365 @@ // test extremes, bounded on both ends - result = search.search(q,new FieldCacheRangeFilter("rand",minRP,maxRP,T,T), numDocs).scoreDocs; + result = search.search(q,FieldCacheRangeFilter.newStringRange("rand",minRP,maxRP,T,T), numDocs).scoreDocs; assertEquals("find all", numDocs, result.length); - result = search.search(q,new FieldCacheRangeFilter("rand",minRP,maxRP,T,F), numDocs).scoreDocs; + result = search.search(q,FieldCacheRangeFilter.newStringRange("rand",minRP,maxRP,T,F), numDocs).scoreDocs; assertEquals("all but biggest", numDocs-1, result.length); - result = search.search(q,new FieldCacheRangeFilter("rand",minRP,maxRP,F,T), numDocs).scoreDocs; + result = search.search(q,FieldCacheRangeFilter.newStringRange("rand",minRP,maxRP,F,T), numDocs).scoreDocs; assertEquals("all but smallest", numDocs-1, result.length); - result = search.search(q,new FieldCacheRangeFilter("rand",minRP,maxRP,F,F), numDocs).scoreDocs; + result = search.search(q,FieldCacheRangeFilter.newStringRange("rand",minRP,maxRP,F,F), numDocs).scoreDocs; assertEquals("all but extremes", numDocs-2, result.length); // unbounded - result = search.search(q,new FieldCacheRangeFilter("rand",minRP,null,T,F), numDocs).scoreDocs; + result = search.search(q,FieldCacheRangeFilter.newStringRange("rand",minRP,null,T,F), numDocs).scoreDocs; assertEquals("smallest and up", numDocs, result.length); - result = search.search(q,new FieldCacheRangeFilter("rand",null,maxRP,F,T), numDocs).scoreDocs; + result = search.search(q,FieldCacheRangeFilter.newStringRange("rand",null,maxRP,F,T), numDocs).scoreDocs; assertEquals("biggest and down", numDocs, result.length); - result = search.search(q,new FieldCacheRangeFilter("rand",minRP,null,F,F), numDocs).scoreDocs; + result = search.search(q,FieldCacheRangeFilter.newStringRange("rand",minRP,null,F,F), numDocs).scoreDocs; assertEquals("not smallest, but up", numDocs-1, result.length); - result = search.search(q,new FieldCacheRangeFilter("rand",null,maxRP,F,F), numDocs).scoreDocs; + result = search.search(q,FieldCacheRangeFilter.newStringRange("rand",null,maxRP,F,F), numDocs).scoreDocs; assertEquals("not biggest, but down", numDocs-1, result.length); // very small sets - result = search.search(q,new FieldCacheRangeFilter("rand",minRP,minRP,F,F), numDocs).scoreDocs; + result = search.search(q,FieldCacheRangeFilter.newStringRange("rand",minRP,minRP,F,F), numDocs).scoreDocs; assertEquals("min,min,F,F", 0, result.length); - result = search.search(q,new FieldCacheRangeFilter("rand",maxRP,maxRP,F,F), numDocs).scoreDocs; + result = search.search(q,FieldCacheRangeFilter.newStringRange("rand",maxRP,maxRP,F,F), numDocs).scoreDocs; assertEquals("max,max,F,F", 0, result.length); - result = search.search(q,new FieldCacheRangeFilter("rand",minRP,minRP,T,T), numDocs).scoreDocs; + result = search.search(q,FieldCacheRangeFilter.newStringRange("rand",minRP,minRP,T,T), numDocs).scoreDocs; assertEquals("min,min,T,T", 1, result.length); - result = search.search(q,new FieldCacheRangeFilter("rand",null,minRP,F,T), numDocs).scoreDocs; + result = search.search(q,FieldCacheRangeFilter.newStringRange("rand",null,minRP,F,T), numDocs).scoreDocs; assertEquals("nul,min,F,T", 1, result.length); - result = search.search(q,new FieldCacheRangeFilter("rand",maxRP,maxRP,T,T), numDocs).scoreDocs; + result = search.search(q,FieldCacheRangeFilter.newStringRange("rand",maxRP,maxRP,T,T), numDocs).scoreDocs; assertEquals("max,max,T,T", 1, result.length); - result = search.search(q,new FieldCacheRangeFilter("rand",maxRP,null,T,F), numDocs).scoreDocs; + result = search.search(q,FieldCacheRangeFilter.newStringRange("rand",maxRP,null,T,F), numDocs).scoreDocs; assertEquals("max,nul,T,T", 1, result.length); } + + // byte-ranges cannot be tested, because all ranges are too big for bytes, need an extra range for that + + public void testFieldCacheRangeFilterShorts() throws IOException { + + IndexReader reader = IndexReader.open(signedIndex.index); + IndexSearcher search = new IndexSearcher(reader); + + int numDocs = reader.numDocs(); + int medId = ((maxId - minId) / 2); + Short minIdO = new Short((short) minId); + Short maxIdO = new Short((short) maxId); + Short medIdO = new Short((short) medId); + + assertEquals("num of docs", numDocs, 1+ maxId - minId); + + ScoreDoc[] result; + Query q = new TermQuery(new Term("body","body")); + + // test id, bounded on both ends + + result = search.search(q,FieldCacheRangeFilter.newShortRange("id",minIdO,maxIdO,T,T), numDocs).scoreDocs; + assertEquals("find all", numDocs, result.length); + + result = search.search(q,FieldCacheRangeFilter.newShortRange("id",minIdO,maxIdO,T,F), numDocs).scoreDocs; + assertEquals("all but last", numDocs-1, result.length); + + result = search.search(q,FieldCacheRangeFilter.newShortRange("id",minIdO,maxIdO,F,T), numDocs).scoreDocs; + assertEquals("all but first", numDocs-1, result.length); + + result = search.search(q,FieldCacheRangeFilter.newShortRange("id",minIdO,maxIdO,F,F), numDocs).scoreDocs; + assertEquals("all but ends", numDocs-2, result.length); + + result = search.search(q,FieldCacheRangeFilter.newShortRange("id",medIdO,maxIdO,T,T), numDocs).scoreDocs; + assertEquals("med and up", 1+ maxId-medId, result.length); + + result = search.search(q,FieldCacheRangeFilter.newShortRange("id",minIdO,medIdO,T,T), numDocs).scoreDocs; + assertEquals("up to med", 1+ medId-minId, result.length); + + // unbounded id + + result = search.search(q,FieldCacheRangeFilter.newShortRange("id",minIdO,null,T,F), numDocs).scoreDocs; + assertEquals("min and up", numDocs, result.length); + + result = search.search(q,FieldCacheRangeFilter.newShortRange("id",null,maxIdO,F,T), numDocs).scoreDocs; + assertEquals("max and down", numDocs, result.length); + + result = search.search(q,FieldCacheRangeFilter.newShortRange("id",minIdO,null,F,F), numDocs).scoreDocs; + assertEquals("not min, but up", numDocs-1, result.length); + + result = search.search(q,FieldCacheRangeFilter.newShortRange("id",null,maxIdO,F,F), numDocs).scoreDocs; + assertEquals("not max, but down", numDocs-1, result.length); + + result = search.search(q,FieldCacheRangeFilter.newShortRange("id",medIdO,maxIdO,T,F), numDocs).scoreDocs; + assertEquals("med and up, not max", maxId-medId, result.length); + + result = search.search(q,FieldCacheRangeFilter.newShortRange("id",minIdO,medIdO,F,T), numDocs).scoreDocs; + assertEquals("not min, up to med", medId-minId, result.length); + + // very small sets + + result = search.search(q,FieldCacheRangeFilter.newShortRange("id",minIdO,minIdO,F,F), numDocs).scoreDocs; + assertEquals("min,min,F,F", 0, result.length); + result = search.search(q,FieldCacheRangeFilter.newShortRange("id",medIdO,medIdO,F,F), numDocs).scoreDocs; + assertEquals("med,med,F,F", 0, result.length); + result = search.search(q,FieldCacheRangeFilter.newShortRange("id",maxIdO,maxIdO,F,F), numDocs).scoreDocs; + assertEquals("max,max,F,F", 0, result.length); + + result = search.search(q,FieldCacheRangeFilter.newShortRange("id",minIdO,minIdO,T,T), numDocs).scoreDocs; + assertEquals("min,min,T,T", 1, result.length); + result = search.search(q,FieldCacheRangeFilter.newShortRange("id",null,minIdO,F,T), numDocs).scoreDocs; + assertEquals("nul,min,F,T", 1, result.length); + + result = search.search(q,FieldCacheRangeFilter.newShortRange("id",maxIdO,maxIdO,T,T), numDocs).scoreDocs; + assertEquals("max,max,T,T", 1, result.length); + result = search.search(q,FieldCacheRangeFilter.newShortRange("id",maxIdO,null,T,F), numDocs).scoreDocs; + assertEquals("max,nul,T,T", 1, result.length); + + result = search.search(q,FieldCacheRangeFilter.newShortRange("id",medIdO,medIdO,T,T), numDocs).scoreDocs; + assertEquals("med,med,T,T", 1, result.length); + + // special cases + result = search.search(q,FieldCacheRangeFilter.newShortRange("id",new Short(Short.MAX_VALUE),null,F,F), numDocs).scoreDocs; + assertEquals("overflow special case", 0, result.length); + result = search.search(q,FieldCacheRangeFilter.newShortRange("id",null,new Short(Short.MIN_VALUE),F,F), numDocs).scoreDocs; + assertEquals("overflow special case", 0, result.length); + result = search.search(q,FieldCacheRangeFilter.newShortRange("id",maxIdO,minIdO,T,T), numDocs).scoreDocs; + assertEquals("inverse range", 0, result.length); + } + + public void testFieldCacheRangeFilterInts() throws IOException { + + IndexReader reader = IndexReader.open(signedIndex.index); + IndexSearcher search = new IndexSearcher(reader); + + int numDocs = reader.numDocs(); + int medId = ((maxId - minId) / 2); + Integer minIdO = new Integer(minId); + Integer maxIdO = new Integer(maxId); + Integer medIdO = new Integer(medId); + + assertEquals("num of docs", numDocs, 1+ maxId - minId); + + ScoreDoc[] result; + Query q = new TermQuery(new Term("body","body")); + + // test id, bounded on both ends + + result = search.search(q,FieldCacheRangeFilter.newIntRange("id",minIdO,maxIdO,T,T), numDocs).scoreDocs; + assertEquals("find all", numDocs, result.length); + + result = search.search(q,FieldCacheRangeFilter.newIntRange("id",minIdO,maxIdO,T,F), numDocs).scoreDocs; + assertEquals("all but last", numDocs-1, result.length); + + result = search.search(q,FieldCacheRangeFilter.newIntRange("id",minIdO,maxIdO,F,T), numDocs).scoreDocs; + assertEquals("all but first", numDocs-1, result.length); + + result = search.search(q,FieldCacheRangeFilter.newIntRange("id",minIdO,maxIdO,F,F), numDocs).scoreDocs; + assertEquals("all but ends", numDocs-2, result.length); + + result = search.search(q,FieldCacheRangeFilter.newIntRange("id",medIdO,maxIdO,T,T), numDocs).scoreDocs; + assertEquals("med and up", 1+ maxId-medId, result.length); + + result = search.search(q,FieldCacheRangeFilter.newIntRange("id",minIdO,medIdO,T,T), numDocs).scoreDocs; + assertEquals("up to med", 1+ medId-minId, result.length); + + // unbounded id + + result = search.search(q,FieldCacheRangeFilter.newIntRange("id",minIdO,null,T,F), numDocs).scoreDocs; + assertEquals("min and up", numDocs, result.length); + + result = search.search(q,FieldCacheRangeFilter.newIntRange("id",null,maxIdO,F,T), numDocs).scoreDocs; + assertEquals("max and down", numDocs, result.length); + + result = search.search(q,FieldCacheRangeFilter.newIntRange("id",minIdO,null,F,F), numDocs).scoreDocs; + assertEquals("not min, but up", numDocs-1, result.length); + + result = search.search(q,FieldCacheRangeFilter.newIntRange("id",null,maxIdO,F,F), numDocs).scoreDocs; + assertEquals("not max, but down", numDocs-1, result.length); + + result = search.search(q,FieldCacheRangeFilter.newIntRange("id",medIdO,maxIdO,T,F), numDocs).scoreDocs; + assertEquals("med and up, not max", maxId-medId, result.length); + + result = search.search(q,FieldCacheRangeFilter.newIntRange("id",minIdO,medIdO,F,T), numDocs).scoreDocs; + assertEquals("not min, up to med", medId-minId, result.length); + + // very small sets + + result = search.search(q,FieldCacheRangeFilter.newIntRange("id",minIdO,minIdO,F,F), numDocs).scoreDocs; + assertEquals("min,min,F,F", 0, result.length); + result = search.search(q,FieldCacheRangeFilter.newIntRange("id",medIdO,medIdO,F,F), numDocs).scoreDocs; + assertEquals("med,med,F,F", 0, result.length); + result = search.search(q,FieldCacheRangeFilter.newIntRange("id",maxIdO,maxIdO,F,F), numDocs).scoreDocs; + assertEquals("max,max,F,F", 0, result.length); + + result = search.search(q,FieldCacheRangeFilter.newIntRange("id",minIdO,minIdO,T,T), numDocs).scoreDocs; + assertEquals("min,min,T,T", 1, result.length); + result = search.search(q,FieldCacheRangeFilter.newIntRange("id",null,minIdO,F,T), numDocs).scoreDocs; + assertEquals("nul,min,F,T", 1, result.length); + + result = search.search(q,FieldCacheRangeFilter.newIntRange("id",maxIdO,maxIdO,T,T), numDocs).scoreDocs; + assertEquals("max,max,T,T", 1, result.length); + result = search.search(q,FieldCacheRangeFilter.newIntRange("id",maxIdO,null,T,F), numDocs).scoreDocs; + assertEquals("max,nul,T,T", 1, result.length); + + result = search.search(q,FieldCacheRangeFilter.newIntRange("id",medIdO,medIdO,T,T), numDocs).scoreDocs; + assertEquals("med,med,T,T", 1, result.length); + + // special cases + result = search.search(q,FieldCacheRangeFilter.newIntRange("id",new Integer(Integer.MAX_VALUE),null,F,F), numDocs).scoreDocs; + assertEquals("overflow special case", 0, result.length); + result = search.search(q,FieldCacheRangeFilter.newIntRange("id",null,new Integer(Integer.MIN_VALUE),F,F), numDocs).scoreDocs; + assertEquals("overflow special case", 0, result.length); + result = search.search(q,FieldCacheRangeFilter.newIntRange("id",maxIdO,minIdO,T,T), numDocs).scoreDocs; + assertEquals("inverse range", 0, result.length); + } + + public void testFieldCacheRangeFilterLongs() throws IOException { + + IndexReader reader = IndexReader.open(signedIndex.index); + IndexSearcher search = new IndexSearcher(reader); + + int numDocs = reader.numDocs(); + int medId = ((maxId - minId) / 2); + Long minIdO = new Long(minId); + Long maxIdO = new Long(maxId); + Long medIdO = new Long(medId); + + assertEquals("num of docs", numDocs, 1+ maxId - minId); + + ScoreDoc[] result; + Query q = new TermQuery(new Term("body","body")); + + // test id, bounded on both ends + + result = search.search(q,FieldCacheRangeFilter.newLongRange("id",minIdO,maxIdO,T,T), numDocs).scoreDocs; + assertEquals("find all", numDocs, result.length); + + result = search.search(q,FieldCacheRangeFilter.newLongRange("id",minIdO,maxIdO,T,F), numDocs).scoreDocs; + assertEquals("all but last", numDocs-1, result.length); + + result = search.search(q,FieldCacheRangeFilter.newLongRange("id",minIdO,maxIdO,F,T), numDocs).scoreDocs; + assertEquals("all but first", numDocs-1, result.length); + + result = search.search(q,FieldCacheRangeFilter.newLongRange("id",minIdO,maxIdO,F,F), numDocs).scoreDocs; + assertEquals("all but ends", numDocs-2, result.length); + + result = search.search(q,FieldCacheRangeFilter.newLongRange("id",medIdO,maxIdO,T,T), numDocs).scoreDocs; + assertEquals("med and up", 1+ maxId-medId, result.length); + + result = search.search(q,FieldCacheRangeFilter.newLongRange("id",minIdO,medIdO,T,T), numDocs).scoreDocs; + assertEquals("up to med", 1+ medId-minId, result.length); + + // unbounded id + + result = search.search(q,FieldCacheRangeFilter.newLongRange("id",minIdO,null,T,F), numDocs).scoreDocs; + assertEquals("min and up", numDocs, result.length); + + result = search.search(q,FieldCacheRangeFilter.newLongRange("id",null,maxIdO,F,T), numDocs).scoreDocs; + assertEquals("max and down", numDocs, result.length); + + result = search.search(q,FieldCacheRangeFilter.newLongRange("id",minIdO,null,F,F), numDocs).scoreDocs; + assertEquals("not min, but up", numDocs-1, result.length); + + result = search.search(q,FieldCacheRangeFilter.newLongRange("id",null,maxIdO,F,F), numDocs).scoreDocs; + assertEquals("not max, but down", numDocs-1, result.length); + + result = search.search(q,FieldCacheRangeFilter.newLongRange("id",medIdO,maxIdO,T,F), numDocs).scoreDocs; + assertEquals("med and up, not max", maxId-medId, result.length); + + result = search.search(q,FieldCacheRangeFilter.newLongRange("id",minIdO,medIdO,F,T), numDocs).scoreDocs; + assertEquals("not min, up to med", medId-minId, result.length); + + // very small sets + + result = search.search(q,FieldCacheRangeFilter.newLongRange("id",minIdO,minIdO,F,F), numDocs).scoreDocs; + assertEquals("min,min,F,F", 0, result.length); + result = search.search(q,FieldCacheRangeFilter.newLongRange("id",medIdO,medIdO,F,F), numDocs).scoreDocs; + assertEquals("med,med,F,F", 0, result.length); + result = search.search(q,FieldCacheRangeFilter.newLongRange("id",maxIdO,maxIdO,F,F), numDocs).scoreDocs; + assertEquals("max,max,F,F", 0, result.length); + + result = search.search(q,FieldCacheRangeFilter.newLongRange("id",minIdO,minIdO,T,T), numDocs).scoreDocs; + assertEquals("min,min,T,T", 1, result.length); + result = search.search(q,FieldCacheRangeFilter.newLongRange("id",null,minIdO,F,T), numDocs).scoreDocs; + assertEquals("nul,min,F,T", 1, result.length); + + result = search.search(q,FieldCacheRangeFilter.newLongRange("id",maxIdO,maxIdO,T,T), numDocs).scoreDocs; + assertEquals("max,max,T,T", 1, result.length); + result = search.search(q,FieldCacheRangeFilter.newLongRange("id",maxIdO,null,T,F), numDocs).scoreDocs; + assertEquals("max,nul,T,T", 1, result.length); + + result = search.search(q,FieldCacheRangeFilter.newLongRange("id",medIdO,medIdO,T,T), numDocs).scoreDocs; + assertEquals("med,med,T,T", 1, result.length); + + // special cases + result = search.search(q,FieldCacheRangeFilter.newLongRange("id",new Long(Long.MAX_VALUE),null,F,F), numDocs).scoreDocs; + assertEquals("overflow special case", 0, result.length); + result = search.search(q,FieldCacheRangeFilter.newLongRange("id",null,new Long(Long.MIN_VALUE),F,F), numDocs).scoreDocs; + assertEquals("overflow special case", 0, result.length); + result = search.search(q,FieldCacheRangeFilter.newLongRange("id",maxIdO,minIdO,T,T), numDocs).scoreDocs; + assertEquals("inverse range", 0, result.length); + } + + // float and double tests are a bit minimalistic, but its complicated, because missing precision + + public void testFieldCacheRangeFilterFloats() throws IOException { + + IndexReader reader = IndexReader.open(signedIndex.index); + IndexSearcher search = new IndexSearcher(reader); + + int numDocs = reader.numDocs(); + Float minIdO = new Float(minId + .5f); + Float medIdO = new Float(minIdO.floatValue() + ((float) (maxId-minId))/2.0f); + + ScoreDoc[] result; + Query q = new TermQuery(new Term("body","body")); + + result = search.search(q,FieldCacheRangeFilter.newFloatRange("id",minIdO,medIdO,T,T), numDocs).scoreDocs; + assertEquals("find all", numDocs/2, result.length); + int count = 0; + result = search.search(q,FieldCacheRangeFilter.newFloatRange("id",null,medIdO,F,T), numDocs).scoreDocs; + count += result.length; + result = search.search(q,FieldCacheRangeFilter.newFloatRange("id",medIdO,null,F,F), numDocs).scoreDocs; + count += result.length; + assertEquals("sum of two concenatted ranges", numDocs, count); + result = search.search(q,FieldCacheRangeFilter.newFloatRange("id",new Float(Float.POSITIVE_INFINITY),null,F,F), numDocs).scoreDocs; + assertEquals("infinity special case", 0, result.length); + result = search.search(q,FieldCacheRangeFilter.newFloatRange("id",null,new Float(Float.NEGATIVE_INFINITY),F,F), numDocs).scoreDocs; + assertEquals("infinity special case", 0, result.length); + } + + public void testFieldCacheRangeFilterDoubles() throws IOException { + + IndexReader reader = IndexReader.open(signedIndex.index); + IndexSearcher search = new IndexSearcher(reader); + + int numDocs = reader.numDocs(); + Double minIdO = new Double(minId + .5); + Double medIdO = new Double(minIdO.floatValue() + ((double) (maxId-minId))/2.0); + + ScoreDoc[] result; + Query q = new TermQuery(new Term("body","body")); + + result = search.search(q,FieldCacheRangeFilter.newDoubleRange("id",minIdO,medIdO,T,T), numDocs).scoreDocs; + assertEquals("find all", numDocs/2, result.length); + int count = 0; + result = search.search(q,FieldCacheRangeFilter.newDoubleRange("id",null,medIdO,F,T), numDocs).scoreDocs; + count += result.length; + result = search.search(q,FieldCacheRangeFilter.newDoubleRange("id",medIdO,null,F,F), numDocs).scoreDocs; + count += result.length; + assertEquals("sum of two concenatted ranges", numDocs, count); + result = search.search(q,FieldCacheRangeFilter.newDoubleRange("id",new Double(Double.POSITIVE_INFINITY),null,F,F), numDocs).scoreDocs; + assertEquals("infinity special case", 0, result.length); + result = search.search(q,FieldCacheRangeFilter.newDoubleRange("id",null, new Double(Double.NEGATIVE_INFINITY),F,F), numDocs).scoreDocs; + assertEquals("infinity special case", 0, result.length); + } + }