Index: contrib/queries/src/java/org/apache/lucene/search/trie/TrieRangeFilter.java =================================================================== --- contrib/queries/src/java/org/apache/lucene/search/trie/TrieRangeFilter.java (revision 737879) +++ contrib/queries/src/java/org/apache/lucene/search/trie/TrieRangeFilter.java (working copy) @@ -39,35 +39,57 @@ /** * Universal constructor (expert use only): Uses already trie-converted min/max values. * You can set min or max (but not both) to null to leave one bound open. + * With minInclusive and maxInclusive can be choosen, if the corresponding + * bound should be included or excluded from the range. */ - public TrieRangeFilter(final String field, final String min, final String max, final TrieUtils variant) { + public TrieRangeFilter(final String field, String min, String max, + final boolean minInclusive, final boolean maxInclusive, final TrieUtils variant + ) { if (min==null && max==null) throw new IllegalArgumentException("The min and max values cannot be both null."); this.trieVariant=variant; + this.field=field.intern(); + // just for toString() this.minUnconverted=min; this.maxUnconverted=max; - this.min=(min==null) ? trieVariant.TRIE_CODED_NUMERIC_MIN : min; - this.max=(max==null) ? trieVariant.TRIE_CODED_NUMERIC_MAX : max; - this.field=field.intern(); + this.minInclusive=minInclusive; + this.maxInclusive=maxInclusive; + // encode bounds + this.min=(min==null) ? trieVariant.TRIE_CODED_NUMERIC_MIN : ( + minInclusive ? min : variant.incrementTrieCoded(min) + ); + this.max=(max==null) ? trieVariant.TRIE_CODED_NUMERIC_MAX : ( + maxInclusive ? max : variant.decrementTrieCoded(max) + ); } /** * Universal constructor (expert use only): Uses already trie-converted min/max values. * You can set min or max (but not both) to null to leave one bound open. - *

This constructor uses the trie package returned by {@link TrieUtils#getDefaultTrieVariant()}. + * With minInclusive and maxInclusive can be choosen, if the corresponding + * bound should be included or excluded from the range. + *

This constructor uses the trie variant returned by {@link TrieUtils#getDefaultTrieVariant()}. */ - public TrieRangeFilter(final String field, final String min, final String max) { - this(field,min,max,TrieUtils.getDefaultTrieVariant()); + public TrieRangeFilter(final String field, final String min, final String max, + final boolean minInclusive, final boolean maxInclusive + ) { + this(field,min,max,minInclusive,maxInclusive,TrieUtils.getDefaultTrieVariant()); } /** * Generates a trie query using the supplied field with range bounds in numeric form (double). * You can set min or max (but not both) to null to leave one bound open. + * With minInclusive and maxInclusive can be choosen, if the corresponding + * bound should be included or excluded from the range. */ - public TrieRangeFilter(final String field, final Double min, final Double max, final TrieUtils variant) { + public TrieRangeFilter(final String field, final Double min, final Double max, + final boolean minInclusive, final boolean maxInclusive, final TrieUtils variant + ) { this( field, (min==null) ? null : variant.doubleToTrieCoded(min.doubleValue()), (max==null) ? null : variant.doubleToTrieCoded(max.doubleValue()), + minInclusive, + maxInclusive, variant ); this.minUnconverted=min; @@ -77,21 +99,31 @@ /** * Generates a trie query using the supplied field with range bounds in numeric form (double). * You can set min or max (but not both) to null to leave one bound open. + * With minInclusive and maxInclusive can be choosen, if the corresponding + * bound should be included or excluded from the range. *

This constructor uses the trie variant returned by {@link TrieUtils#getDefaultTrieVariant()}. */ - public TrieRangeFilter(final String field, final Double min, final Double max) { - this(field,min,max,TrieUtils.getDefaultTrieVariant()); + public TrieRangeFilter(final String field, final Double min, final Double max, + final boolean minInclusive, final boolean maxInclusive + ) { + this(field,min,max,minInclusive,maxInclusive,TrieUtils.getDefaultTrieVariant()); } /** * Generates a trie query using the supplied field with range bounds in date/time form. * You can set min or max (but not both) to null to leave one bound open. + * With minInclusive and maxInclusive can be choosen, if the corresponding + * bound should be included or excluded from the range. */ - public TrieRangeFilter(final String field, final Date min, final Date max, final TrieUtils variant) { + public TrieRangeFilter(final String field, final Date min, final Date max, + final boolean minInclusive, final boolean maxInclusive, final TrieUtils variant + ) { this( field, (min==null) ? null : variant.dateToTrieCoded(min), (max==null) ? null : variant.dateToTrieCoded(max), + minInclusive, + maxInclusive, variant ); this.minUnconverted=min; @@ -101,21 +133,31 @@ /** * Generates a trie query using the supplied field with range bounds in date/time form. * You can set min or max (but not both) to null to leave one bound open. + * With minInclusive and maxInclusive can be choosen, if the corresponding + * bound should be included or excluded from the range. *

This constructor uses the trie variant returned by {@link TrieUtils#getDefaultTrieVariant()}. */ - public TrieRangeFilter(final String field, final Date min, final Date max) { - this(field,min,max,TrieUtils.getDefaultTrieVariant()); + public TrieRangeFilter(final String field, final Date min, final Date max, + final boolean minInclusive, final boolean maxInclusive + ) { + this(field,min,max,minInclusive,maxInclusive,TrieUtils.getDefaultTrieVariant()); } /** * Generates a trie query using the supplied field with range bounds in integer form (long). * You can set min or max (but not both) to null to leave one bound open. + * With minInclusive and maxInclusive can be choosen, if the corresponding + * bound should be included or excluded from the range. */ - public TrieRangeFilter(final String field, final Long min, final Long max, final TrieUtils variant) { + public TrieRangeFilter(final String field, final Long min, final Long max, + final boolean minInclusive, final boolean maxInclusive, final TrieUtils variant + ) { this( field, (min==null) ? null : variant.longToTrieCoded(min.longValue()), (max==null) ? null : variant.longToTrieCoded(max.longValue()), + minInclusive, + maxInclusive, variant ); this.minUnconverted=min; @@ -125,10 +167,14 @@ /** * Generates a trie query using the supplied field with range bounds in integer form (long). * You can set min or max (but not both) to null to leave one bound open. + * With minInclusive and maxInclusive can be choosen, if the corresponding + * bound should be included or excluded from the range. *

This constructor uses the trie variant returned by {@link TrieUtils#getDefaultTrieVariant()}. */ - public TrieRangeFilter(final String field, final Long min, final Long max) { - this(field,min,max,TrieUtils.getDefaultTrieVariant()); + public TrieRangeFilter(final String field, final Long min, final Long max, + final boolean minInclusive, final boolean maxInclusive + ) { + this(field,min,max,minInclusive,maxInclusive,TrieUtils.getDefaultTrieVariant()); } //@Override @@ -139,14 +185,21 @@ public String toString(final String field) { final StringBuffer sb=new StringBuffer(); if (!this.field.equals(field)) sb.append(this.field).append(':'); - return sb.append('[').append(minUnconverted).append(" TO ").append(maxUnconverted).append(']').toString(); + return sb.append(minInclusive ? '[' : '{') + .append((minUnconverted==null) ? "*" : minUnconverted.toString()) + .append(" TO ") + .append((maxUnconverted==null) ? "*" : maxUnconverted.toString()) + .append(maxInclusive ? ']' : '}').toString(); } //@Override public final boolean equals(final Object o) { if (o instanceof TrieRangeFilter) { TrieRangeFilter q=(TrieRangeFilter)o; - // trieVariants are singleton per type, so no equals needed + /* trieVariants are singleton per type, so no equals needed. + * TrieRangeFilters with different datatypes but same encoded + * bounds (with increment/decrement for exclusive) are considered equal. + */ return (field==q.field && min.equals(q.min) && max.equals(q.max) && trieVariant==q.trieVariant); } else return false; } @@ -282,6 +335,7 @@ // members private final String field,min,max; private final TrieUtils trieVariant; + private final boolean minInclusive,maxInclusive; private Object minUnconverted,maxUnconverted; private int lastNumberOfTerms=-1; } Index: contrib/queries/src/java/org/apache/lucene/search/trie/TrieRangeQuery.java =================================================================== --- contrib/queries/src/java/org/apache/lucene/search/trie/TrieRangeQuery.java (revision 737879) +++ contrib/queries/src/java/org/apache/lucene/search/trie/TrieRangeQuery.java (working copy) @@ -34,69 +34,101 @@ /** * Universal constructor (expert use only): Uses already trie-converted min/max values. * You can set min or max (but not both) to null to leave one bound open. + * With minInclusive and maxInclusive can be choosen, if the corresponding + * bound should be included or excluded from the range. *

This constructor uses the trie variant returned by {@link TrieUtils#getDefaultTrieVariant()}. */ - public TrieRangeQuery(final String field, final String min, final String max) { - super(new TrieRangeFilter(field,min,max)); + public TrieRangeQuery(final String field, final String min, final String max, + final boolean minInclusive, final boolean maxInclusive + ) { + super(new TrieRangeFilter(field,min,max,minInclusive,maxInclusive)); } /** * Universal constructor (expert use only): Uses already trie-converted min/max values. * You can set min or max (but not both) to null to leave one bound open. + * With minInclusive and maxInclusive can be choosen, if the corresponding + * bound should be included or excluded from the range. */ - public TrieRangeQuery(final String field, final String min, final String max, final TrieUtils variant) { - super(new TrieRangeFilter(field,min,max,variant)); + public TrieRangeQuery(final String field, final String min, final String max, + final boolean minInclusive, final boolean maxInclusive, final TrieUtils variant + ) { + super(new TrieRangeFilter(field,min,max,minInclusive,maxInclusive,variant)); } /** * A trie query using the supplied field with range bounds in numeric form (double). * You can set min or max (but not both) to null to leave one bound open. + * With minInclusive and maxInclusive can be choosen, if the corresponding + * bound should be included or excluded from the range. *

This constructor uses the trie variant returned by {@link TrieUtils#getDefaultTrieVariant()}. */ - public TrieRangeQuery(final String field, final Double min, final Double max) { - super(new TrieRangeFilter(field,min,max)); + public TrieRangeQuery(final String field, final Double min, final Double max, + final boolean minInclusive, final boolean maxInclusive + ) { + super(new TrieRangeFilter(field,min,max,minInclusive,maxInclusive)); } /** * A trie query using the supplied field with range bounds in numeric form (double). * You can set min or max (but not both) to null to leave one bound open. + * With minInclusive and maxInclusive can be choosen, if the corresponding + * bound should be included or excluded from the range. */ - public TrieRangeQuery(final String field, final Double min, final Double max, final TrieUtils variant) { - super(new TrieRangeFilter(field,min,max,variant)); + public TrieRangeQuery(final String field, final Double min, final Double max, + final boolean minInclusive, final boolean maxInclusive, final TrieUtils variant + ) { + super(new TrieRangeFilter(field,min,max,minInclusive,maxInclusive,variant)); } /** * A trie query using the supplied field with range bounds in date/time form. * You can set min or max (but not both) to null to leave one bound open. + * With minInclusive and maxInclusive can be choosen, if the corresponding + * bound should be included or excluded from the range. *

This constructor uses the trie variant returned by {@link TrieUtils#getDefaultTrieVariant()}. */ - public TrieRangeQuery(final String field, final Date min, final Date max) { - super(new TrieRangeFilter(field,min,max)); + public TrieRangeQuery(final String field, final Date min, final Date max, + final boolean minInclusive, final boolean maxInclusive + ) { + super(new TrieRangeFilter(field,min,max,minInclusive,maxInclusive)); } /** * A trie query using the supplied field with range bounds in date/time form. * You can set min or max (but not both) to null to leave one bound open. + * With minInclusive and maxInclusive can be choosen, if the corresponding + * bound should be included or excluded from the range. */ - public TrieRangeQuery(final String field, final Date min, final Date max, final TrieUtils variant) { - super(new TrieRangeFilter(field,min,max,variant)); + public TrieRangeQuery(final String field, final Date min, final Date max, + final boolean minInclusive, final boolean maxInclusive, final TrieUtils variant + ) { + super(new TrieRangeFilter(field,min,max,minInclusive,maxInclusive,variant)); } /** * A trie query using the supplied field with range bounds in integer form (long). * You can set min or max (but not both) to null to leave one bound open. + * With minInclusive and maxInclusive can be choosen, if the corresponding + * bound should be included or excluded from the range. *

This constructor uses the trie variant returned by {@link TrieUtils#getDefaultTrieVariant()}. */ - public TrieRangeQuery(final String field, final Long min, final Long max) { - super(new TrieRangeFilter(field,min,max)); + public TrieRangeQuery(final String field, final Long min, final Long max, + final boolean minInclusive, final boolean maxInclusive + ) { + super(new TrieRangeFilter(field,min,max,minInclusive,maxInclusive)); } /** * A trie query using the supplied field with range bounds in integer form (long). * You can set min or max (but not both) to null to leave one bound open. + * With minInclusive and maxInclusive can be choosen, if the corresponding + * bound should be included or excluded from the range. */ - public TrieRangeQuery(final String field, final Long min, final Long max, final TrieUtils variant) { - super(new TrieRangeFilter(field,min,max,variant)); + public TrieRangeQuery(final String field, final Long min, final Long max, + final boolean minInclusive, final boolean maxInclusive, final TrieUtils variant + ) { + super(new TrieRangeFilter(field,min,max,minInclusive,maxInclusive,variant)); } /** Index: contrib/queries/src/test/org/apache/lucene/search/trie/TestTrieRangeQuery.java =================================================================== --- contrib/queries/src/test/org/apache/lucene/search/trie/TestTrieRangeQuery.java (revision 737879) +++ contrib/queries/src/test/org/apache/lucene/search/trie/TestTrieRangeQuery.java (working copy) @@ -58,7 +58,7 @@ TrieUtils.VARIANT_2BIT.addLongTrieCodedDocumentField( doc, "field2", distance*l, true /*index it*/, Field.Store.YES ); - // add ascending fields with a distance of 1 to test the correct splitting of range + // add ascending fields with a distance of 1 to test the correct splitting of range and inclusive/exclusive TrieUtils.VARIANT_8BIT.addLongTrieCodedDocumentField( doc, "ascfield8", l, true /*index it*/, Field.Store.NO ); @@ -83,7 +83,7 @@ String field="field"+variant.TRIE_BITS; int count=3000; long lower=96666L, upper=lower + count*distance + 1234L; - TrieRangeQuery q=new TrieRangeQuery(field, new Long(lower), new Long(upper), variant); + TrieRangeQuery q=new TrieRangeQuery(field, new Long(lower), new Long(upper), true, true, variant); TopDocs topDocs = searcher.search(q, null, 10000, Sort.INDEXORDER); System.out.println("Found "+q.getLastNumberOfTerms()+" distinct terms in range for field '"+field+"'."); ScoreDoc[] sd = topDocs.scoreDocs; @@ -111,7 +111,7 @@ String field="field"+variant.TRIE_BITS; int count=3000; long upper=(count-1)*distance + 1234L; - TrieRangeQuery q=new TrieRangeQuery(field, null, new Long(upper), variant); + TrieRangeQuery q=new TrieRangeQuery(field, null, new Long(upper), true, true, variant); TopDocs topDocs = searcher.search(q, null, 10000, Sort.INDEXORDER); System.out.println("Found "+q.getLastNumberOfTerms()+" distinct terms in left open range for field '"+field+"'."); ScoreDoc[] sd = topDocs.scoreDocs; @@ -141,12 +141,34 @@ for (int i=0; i<50; i++) { long lower=(long)(rnd.nextDouble()*10000L*distance); long upper=(long)(rnd.nextDouble()*10000L*distance); - TrieRangeQuery tq=new TrieRangeQuery(field, new Long(lower), new Long(upper), variant); + // test inclusive range + TrieRangeQuery tq=new TrieRangeQuery(field, new Long(lower), new Long(upper), true, true, variant); RangeQuery cq=new RangeQuery(field, variant.longToTrieCoded(lower), variant.longToTrieCoded(upper), true, true); cq.setConstantScoreRewrite(true); TopDocs tTopDocs = searcher.search(tq, 1); TopDocs cTopDocs = searcher.search(cq, 1); assertEquals("Returned count for TrieRangeQuery and RangeQuery must be equal", tTopDocs.totalHits, cTopDocs.totalHits ); + // test exclusive range + tq=new TrieRangeQuery(field, new Long(lower), new Long(upper), false, false, variant); + cq=new RangeQuery(field, variant.longToTrieCoded(lower), variant.longToTrieCoded(upper), false, false); + cq.setConstantScoreRewrite(true); + tTopDocs = searcher.search(tq, 1); + cTopDocs = searcher.search(cq, 1); + assertEquals("Returned count for TrieRangeQuery and RangeQuery must be equal", tTopDocs.totalHits, cTopDocs.totalHits ); + // test left exclusive range + tq=new TrieRangeQuery(field, new Long(lower), new Long(upper), false, true, variant); + cq=new RangeQuery(field, variant.longToTrieCoded(lower), variant.longToTrieCoded(upper), false, true); + cq.setConstantScoreRewrite(true); + tTopDocs = searcher.search(tq, 1); + cTopDocs = searcher.search(cq, 1); + assertEquals("Returned count for TrieRangeQuery and RangeQuery must be equal", tTopDocs.totalHits, cTopDocs.totalHits ); + // test right exclusive range + tq=new TrieRangeQuery(field, new Long(lower), new Long(upper), true, false, variant); + cq=new RangeQuery(field, variant.longToTrieCoded(lower), variant.longToTrieCoded(upper), true, false); + cq.setConstantScoreRewrite(true); + tTopDocs = searcher.search(tq, 1); + cTopDocs = searcher.search(cq, 1); + assertEquals("Returned count for TrieRangeQuery and RangeQuery must be equal", tTopDocs.totalHits, cTopDocs.totalHits ); } } @@ -171,9 +193,22 @@ if (lower>upper) { long a=lower; lower=upper; upper=a; } - TrieRangeQuery tq=new TrieRangeQuery(field, new Long(lower), new Long(upper), variant); + // test inclusive range + TrieRangeQuery tq=new TrieRangeQuery(field, new Long(lower), new Long(upper), true, true, variant); TopDocs tTopDocs = searcher.search(tq, 1); assertEquals("Returned count of range query must be equal to inclusive range length", tTopDocs.totalHits, upper-lower+1 ); + // test exclusive range + tq=new TrieRangeQuery(field, new Long(lower), new Long(upper), false, false, variant); + tTopDocs = searcher.search(tq, 1); + assertEquals("Returned count of range query must be equal to exclusive range length", tTopDocs.totalHits, upper-lower-1 ); + // test left exclusive range + tq=new TrieRangeQuery(field, new Long(lower), new Long(upper), false, true, variant); + tTopDocs = searcher.search(tq, 1); + assertEquals("Returned count of range query must be equal to half exclusive range length", tTopDocs.totalHits, upper-lower ); + // test right exclusive range + tq=new TrieRangeQuery(field, new Long(lower), new Long(upper), true, false, variant); + tTopDocs = searcher.search(tq, 1); + assertEquals("Returned count of range query must be equal to half exclusive range length", tTopDocs.totalHits, upper-lower ); } } @@ -199,7 +234,7 @@ if (lower>upper) { long a=lower; lower=upper; upper=a; } - TrieRangeQuery tq=new TrieRangeQuery(field, new Long(lower), new Long(upper), variant); + TrieRangeQuery tq=new TrieRangeQuery(field, new Long(lower), new Long(upper), true, true, variant); TopDocs topDocs = searcher.search(tq, null, 10000, new Sort(variant.getSortField(field, true))); if (topDocs.totalHits==0) continue; ScoreDoc[] sd = topDocs.scoreDocs;