Of course, not only did I manage to forget to include the attachment, but when I sent a reply with the code, mail.apache.org rejected it because it was a ZIP file.
So let's see how mail.apache.or feels about 6 seperate text files. : Date: Mon, 22 Nov 2004 18:25:24 -0800 (PST) : Subject: Numeric Range Restrictions: Queries vs Filters : : (NOTE: numbers in [] indicate Footnotes) : : I'm rather new to Lucene (and this list), so if I'm grossly : misunderstanding things, forgive me. : : One of my main needs as I investigate Search technologies is to restrict : results based on Ranges of numeric values. Looking over the archives of : this list, it seems that lots of people have run into problems dealing : with this. In particular, whenever someone asks a question about "Numeric : Ranges" the question seem to always involve one (or more) of the : following: : : (a) Lexical sorting puts 11 in the range "1 TO 5" : (b) Dates (or Dates and Times) : (c) BooleanQuery$TooManyClauses Exceptions : (d) Should I use a filter? : : (a) is a solved problem as long as you use a formatter like : LongField.java[1] : : (b) is really nothing more then a special case of dealing with generic : numeric values. While there are certainly special purposes solutions that : sometimes apply to dealing with Date ranges, any good solution for dealing : with raw numeric ranges can be applied to Dates (and Times) : : (c) is a situation that seems to come up a lot because of the way : RangeQuery works. The rewrite method walks all of the Terms in the index : starting with "lowerTerm" and builds up BooleanQuery containing a separate : TermQuery for every Term found, until it reaches the upperTerm. This : causes a range search of "0001 TO 1000" to generate a BooleanQuery with N : clauses, where N is the quantity of unique values in the field which are : lexically greater then 0001 and lexically less then 1000. depending on : the nature of your data, this might be 0 BooleanClauses, or it might be : 1000 BooleanClauses; but the list is built before the search is ever even : executed. : : At first, this may seem really strange -- I know I was certainly confused : -- but there is a very good reason for it: Ultimately RangeQuery still : provides you with a meaningful score for each document, based on the : frequency (and quantity) of terms that document has in the range [2]. In : order to do that, it has to expand itself, but what if you don't care if : your Range restriction impacts the Score? [3] : : Which brings us to... : : (c) Filtering. Filters in general make a lot of sense to me. They are a : way to specify (at query time) that only a certain subset of the index : should be considered for results. The Filter class has a very straight : forward API that seems very easy to subclass to get the behavior I want. : The Query API on the other hand ... I freely admit, that I can't make : heads or tails out of it. I don't even know where I would begin to try : and write a new subclass of Query if I wanted to. : : I would think that most people who want to do a "numeric range : restriction" on their data, probably don't care about the Scoring benefits : of RangeQuery. Looking at the code base, the way DateFilter works seems : like it provides an ideal solution to any sort of Range restriction (not : just Dates) that *should* be more efficient then using RangeQuery when : dealing with an unbounded value set. (Both approaches need to iterate over : all of the terms in the specified field using TermEnum, but RangeQuery has : to build up an set of BooleanQuery objects for each matching term, and : then each of those queries have to help score the documents -- DateFilter : on the other hand only has to maintain a single BitSet of documents that : it finds as it iterates) : : But I was surprised then to see the following quote from "Erik Hatcher" in : the archives: : : "In fact, DateFilter by itself is practically of no use, I think." [4] : : ...Erik goes on to suggest that given "a set of canned date ranges", it : doesn't really matter if you use a RangeQuery or a DateFilter -- as long : as you cache them to reuse them (with something like CachingWrappingFilter : or QueryFilter). I'm hoping that he might elaborate on that comment? : : As a test, I wrote a "RangeFilter" which borrows heavily from DateFilter : to both convince myself it could work, and to do a comparison between it : and RangeQuery. [5] Based on my limited tests, using a Filter to restrict : to a Range is a lot faster then using RangeQuery -- independent of : caching. : : The attachment contains my RangeFilter, a unit test that demonstrates it, : and a Benchmarking unit test that does a side-by-side comparison with : RangeQuery [6]. If developers feel that this class is useful, then by all : means roll it into the code base. (90% of it is cut/pasted from : DateFilter/RangeQuery anyway) : : : Comments? ... Questions? ... Answers? : : : : Footnotes: : : [1] It seems to me this class is extremely useful, does anyone know : if there's a particular reason it hasn't been added to the main Lucene : codebase? : http://www.mail-archive.com/lucene-dev@jakarta.apache.org/msg04790.html : : [2] Take a look at RangeQueryScoreDemo.java in the attachment, which : produces output something like this... : Range Search for: 'apple' TO 'dog' : 0.40924072 ... bed dog emu : 0.38014847 ... DOG : 0.2825246 ... cat : 0.17657787 ... apple emu : 0.12671615 ... dog : : [3] According to the list archives "Matt Quail" mentioned in May that : he was working on a "QuickRangeQuery" class that wouldn't have the : BooleanQuery limitation, at the expense of always scoring "1.0", : but I haven't seen any mention of anything like it since. Is Matt : still an active list member? Matt, is this something you're still : pursuing? : http://nagoya.apache.org/eyebrowse/ReadMsg?msgId=1659395 : : [4] http://www.mail-archive.com/lucene-user@jakarta.apache.org/msg07015.html : : [5] The only major difference between my RangeFilter and DateFilter is : that RangeFilter supports options for inclusion/exclusion : (individually for the low/high terms I might add). But for the : purposes of a benchmark, doing the same thing with DateFilter would : have worked fine. : : [6] If you have ant, see "ant -projecthelp"; otherwise, read the top : of build.xml : : : : -- : : ------------------------------------------------------------------- : "Oh, you're a tricky one." Chris M Hostetter : -- Trisha Weir [EMAIL PROTECTED] : : -- ------------------------------------------------------------------- "Oh, you're a tricky one." Chris M Hostetter -- Trisha Weir [EMAIL PROTECTED]
<project name="proto-rangefilter" default="classes"> <description> A RangeFilter class for Lucene, along with some simple JUnit classes to test it provide a comparison benchmark against RangeQuery. You will need to modify the "my.classpath" declaration in this build.xml before it will do much for you. Included Files: RangeQueryScoreDemo.java - standalone demo of RangeQuery's scoring RangeFilter.java - main code of the Filter BaseTestRangeFilter.java - base class JUnit test that build a RAM index BenchTestRangeFilter.java - benchmark written as a JUnit test TestRangeFilter.java - JUnit test of RangeFilter build.xml - this file </description> <path id="my.classpath"> <!-- modify classpath as neccessary --> <!-- the main things needed are lucene 1.4.2 and junit --> <pathelement path="."/> <pathelement location="../lucene/lucene-1.4.2/lucene-1.4.2.jar"/> <fileset dir="../../code/cvs/ssa/java/dist/" includes="**/*.jar" /> </path> <target name="classes" description="Compiles all the code." > <javac srcdir="." destdir="." debug="true"> <classpath refid="my.classpath" /> </javac> </target> <target name="test" depends="classes" description="Run unit tests"> <junit printsummary="on"> <test name="TestRangeFilter" /> <formatter type="plain" usefile="false" /> <classpath refid="my.classpath" /> </junit> </target> <target name="bench" depends="classes" description="Run simple benchmark of RangeQuery vs RangeFilter"> <junit printsummary="on"> <test name="BenchTestRangeFilter" /> <formatter type="plain" usefile="false" /> <classpath refid="my.classpath" /> </junit> </target> <target name="demo" depends="classes" description="Run a demo of RangeQuery and the way it scores"> <java classname="RangeQueryScoreDemo"> <classpath refid="my.classpath" /> </java> </target> </project>
import org.apache.lucene.index.Term; import org.apache.lucene.index.IndexReader; import org.apache.lucene.index.IndexWriter; import org.apache.lucene.search.Query; import org.apache.lucene.search.Hits; import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.store.RAMDirectory; import org.apache.lucene.analysis.SimpleAnalyzer; import org.apache.lucene.document.Document; import org.apache.lucene.document.Field; import org.apache.lucene.document.DateField; import org.apache.lucene.search.Query; import org.apache.lucene.search.TermQuery; import org.apache.lucene.search.Filter; import org.apache.lucene.search.DateFilter; import java.io.IOException; import java.util.Random; import junit.framework.TestCase; public class BaseTestRangeFilter extends TestCase { public static final boolean F = false; public static final boolean T = true; RAMDirectory index = new RAMDirectory(); Random rand = new Random(101); // use a set seed to test is deterministic int maxR = Integer.MIN_VALUE; int minR = Integer.MAX_VALUE; int minId = 0; int maxId = 10000; static final int intLength = Integer.toString(Integer.MAX_VALUE).length(); /** * a simple padding function that should work with any int */ public static String pad(int n) { StringBuffer b = new StringBuffer(40); String p = "0"; if (n < 0) { p = "-"; n = Integer.MAX_VALUE + n + 1; } b.append(p); String s = Integer.toString(n); for (int i = s.length(); i <= intLength; i++) { b.append("0"); } b.append(s); return b.toString(); } public BaseTestRangeFilter(String name) { super(name); build(); } public BaseTestRangeFilter() { build(); } private void build() { try { /* build an index */ IndexWriter writer = new IndexWriter(index, new SimpleAnalyzer(), T); for (int d = minId; d <= maxId; d++) { Document doc = new Document(); doc.add(Field.Keyword("id",pad(d))); int r= rand.nextInt(); if (maxR < r) { maxR = r; } if (r < minR) { minR = r; } doc.add(Field.Keyword("rand",pad(r))); doc.add(Field.Keyword("body","body")); writer.addDocument(doc); } writer.optimize(); writer.close(); } catch (Exception e) { throw new RuntimeException("can't build index", e); } } public void testPad() { int[] tests = new int[] { -9999999, -99560, -100, -3, -1, 0, 3, 9, 10, 1000, 999999999 }; for (int i = 0; i < tests.length - 1; i++) { int a = tests[i]; int b = tests[i+1]; String aa = pad(a); String bb = pad(b); String label = a + ":" + aa + " vs " + b + ":" + bb; assertEquals("length of " + label, aa.length(), bb.length()); assertTrue("compare less than " + label, aa.compareTo(bb) < 0); } } }
import org.apache.lucene.index.Term; import org.apache.lucene.index.IndexReader; import org.apache.lucene.index.IndexWriter; import org.apache.lucene.search.Query; import org.apache.lucene.search.Hits; import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.store.RAMDirectory; import org.apache.lucene.analysis.SimpleAnalyzer; import org.apache.lucene.document.Document; import org.apache.lucene.document.Field; import org.apache.lucene.document.DateField; import org.apache.lucene.search.Query; import org.apache.lucene.search.TermQuery; import org.apache.lucene.search.BooleanQuery; import org.apache.lucene.search.RangeQuery; import org.apache.lucene.search.Filter; import org.apache.lucene.search.DateFilter; import java.io.IOException; import java.util.Random; import junit.framework.TestCase; public class BenchTestRangeFilter extends BaseTestRangeFilter { /** use a fixed seed so tests are deterministic and comparable */ public static final int SEED = 23; public static final int ITERS = 100; public BenchTestRangeFilter(String name) { super(name); } public BenchTestRangeFilter() { super(); } /** * test the execution of several queries using the RangeFilter */ public void testRangeFilter() throws IOException { doTest (new TestSearcher () { public Hits search(IndexSearcher s, Query q, String f, String low, String high, boolean inclusive) throws IOException { Filter ff = new RangeFilter(f, low, high, inclusive, inclusive); return s.search(q,ff); } }); } /** * test the execution of several queries using RangeQuery */ public void testRangeQuery() throws IOException { /* make sure RangeQuery will work with the max possible range size */ BooleanQuery.setMaxClauseCount(maxId + 1); doTest (new TestSearcher () { public Hits search(IndexSearcher s, Query q, String f, String low, String high, boolean inclusive) throws IOException { Query r = new RangeQuery(new Term(f, low), new Term(f, high), inclusive); BooleanQuery qq = new BooleanQuery(); qq.add(q,true,false); qq.add(r,true,false); return s.search(qq); } }); } protected void doTest(TestSearcher tester) throws IOException { Random r = new Random(SEED); IndexReader reader = IndexReader.open(index); IndexSearcher search = new IndexSearcher(reader); int[] counts = new int[ITERS]; /* pick some random Id ranges that are small and search them */ for (int i =0; i < ITERS; i++) { int a = minId + r.nextInt((maxId - minId) / 2); int b = a + 100; String aa = pad(a); String bb = pad(b); Hits result; Query q = new TermQuery(new Term("body","body")); result = tester.search(search,q,"id",aa,bb,true); counts[i] = result.length(); } /* pick some random Id ranges that are (on average) half * the size of the index and search them */ for (int i =0; i < ITERS; i++) { int a = minId + r.nextInt((maxId - minId) / 2); int b = maxId - r.nextInt((maxId - minId) / 2); String aa = pad(a); String bb = pad(b); Hits result; Query q = new TermQuery(new Term("body","body")); result = tester.search(search,q,"id",aa,bb,true); counts[i] = result.length(); } /* pick some random random ranges that are small and search them */ for (int i =0; i < ITERS; i++) { int a = minR + r.nextInt(maxR / 100); int b = a + 500; String aa = pad(a); String bb = pad(b); Hits result; Query q = new TermQuery(new Term("body","body")); result = tester.search(search,q,"rand",aa,bb,true); counts[i] = result.length(); } /* pick some random Id ranges that are (on average) half * the size of the index and search them */ for (int i =0; i < ITERS; i++) { int a = minR + r.nextInt((maxR / 2) - (minR / 2) - 1); int b = maxR - r.nextInt((maxR / 2) - (minR / 2) - 1); String aa = pad(a); String bb = pad(b); Hits result; Query q = new TermQuery(new Term("body","body")); result = tester.search(search,q,"rand",aa,bb,true); counts[i] = result.length(); } } public interface TestSearcher { public Hits search(IndexSearcher s, Query q, String f, String low, String high, boolean inclusive) throws IOException; } }
/** * * This code borrows heavily from RangeQuery.java and DateFilter.java * available here... * http://jakarta.apache.org/lucene/ * * This is the (c) and licence from those files.... * * * Copyright 2004 The Apache Software Foundation * * Licensed 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.util.BitSet; import java.util.Date; import java.io.IOException; import org.apache.lucene.search.Filter; import org.apache.lucene.index.Term; import org.apache.lucene.index.TermDocs; import org.apache.lucene.index.TermEnum; import org.apache.lucene.index.IndexReader; /** * A Filter that restricts search results to a range of values in a given * fieled. * * <p> * This code borrows heavily from RangeQuery, but implimented as a Filter * (much like DateFilter) * </p> * <p> * In theory, the advantage of using RangeFilter instead of a RangeQuery * is that while both use a TermEnum to walk the index from the low end * of the range to hte high end, RangeQuery does this to build up a list * of queries, which then all have to be searched against (to compute * score). A Filter on the other hand doesn't care about scoring (and * in my mind -- neither do must people who want to do RAnge restrictions * in their searches) so as it walks the terms itcan just build up the * resulting BitSet of docs that contain terms in the range. * </p> * * <p> * Since DateFilter's constructor only deals with strings, you could just * use it instead; but this class has the added advantage of allowing you * to specify wether the upper of lower bounds should be included -- * independantly. * </p> */ public class RangeFilter extends Filter { private String f; private String low; private String upp; private boolean inclLower; private boolean inclUpper; /** * @param field The field this range applies to * @param lower The lower bound on this range * @param upper The upper bound on this range * @param includeLower Does this range include the lower bound? * @param includeUpper Does this range include the upper bound? */ public RangeFilter(String field, String lower, String upper, boolean includeLower, boolean includeUpper) { f = field; low = lower; upp = upper; inclLower = includeLower; inclUpper = includeUpper; if (null == low && null == upp) { throw new IllegalArgumentException ("At least one value must be non-null"); } if (inclLower && null == low) { throw new IllegalArgumentException ("The lower bound must be non-null to be inclusive"); } if (inclUpper && null == upp) { throw new IllegalArgumentException ("The upper bound must be non-null to be inclusive"); } } /** * Constructs a filter for field <code>field</code> matching * less than or equal to <code>value</code> */ public static RangeFilter Less(String field, String upper) { return new RangeFilter(field, null, upper, false, true); } /** * Constructs a filter for field <code>field</code> matching * greater than or equal to <code>lower</code> */ public static RangeFilter More(String field, String lower) { return new RangeFilter(field, lower, null, true, false); } /** * Returns a BitSet with true for documents which should be * permitted in search results, and false for those that should * not. */ public BitSet bits(IndexReader reader) throws IOException { BitSet bits = new BitSet(reader.maxDoc()); TermEnum enumerator = (null != low ? reader.terms(new Term(f, low)) : reader.terms(new Term(f,""))); try { if (enumerator.term() == null) { return bits; } boolean checkLower = false; if (!inclLower) // make adjustments to set to exclusive checkLower = true; TermDocs termDocs = reader.termDocs(); try { do { Term term = enumerator.term(); if (term != null && term.field().equals(f)) { if (!checkLower || null==low || term.text().compareTo(low) > 0) { checkLower = false; if (upp != null) { int compare = upp.compareTo(term.text()); /* if beyond the upper term, or is exclusive and * this is equal to the upper term, break out */ if ((compare < 0) || (!inclUpper && compare==0)) { break; } } /* we have a good term, find the docs */ termDocs.seek(enumerator.term()); while (termDocs.next()) { bits.set(termDocs.doc()); } } } else { break; } } while (enumerator.next()); } finally { termDocs.close(); } } finally { enumerator.close(); } return bits; } public String toString() { StringBuffer buffer = new StringBuffer(); buffer.append(f); buffer.append(":"); buffer.append(inclLower ? "[" : "{"); if (null != low) { buffer.append(low); } buffer.append("-"); if (null != upp) { buffer.append(upp); } buffer.append(inclUpper ? "]" : "}"); return buffer.toString(); } }
import org.apache.lucene.index.Term; import org.apache.lucene.index.IndexReader; import org.apache.lucene.index.IndexWriter; import org.apache.lucene.search.Query; import org.apache.lucene.search.Hits; import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.store.RAMDirectory; import org.apache.lucene.analysis.SimpleAnalyzer; import org.apache.lucene.document.Document; import org.apache.lucene.document.Field; import org.apache.lucene.search.Query; import org.apache.lucene.search.RangeQuery; import java.io.IOException; import java.util.Random; /** * Simple demo of *why* range query expands itself out into a * BooleanQuery containing many, many, MANY terms (all of hte known * terms between the low/high terms of the range) */ public class RangeQueryScoreDemo { public static void main(String argv[]) throws IOException { Document doc; RAMDirectory d = new RAMDirectory(); IndexWriter w = new IndexWriter(d, new SimpleAnalyzer(), true); doc = new Document(); doc.add(Field.Text("words", "not in range")); w.addDocument(doc); doc = new Document(); doc.add(Field.Text("words", "apple emu")); w.addDocument(doc); doc = new Document(); doc.add(Field.Text("words", "bed dog emu")); w.addDocument(doc); doc = new Document(); doc.add(Field.Text("words", "dog")); w.addDocument(doc); doc = new Document(); doc.add(Field.Text("words", "cat")); w.addDocument(doc); doc = new Document(); Field f = Field.Text("words", "DOG"); /* caps denote the boost */ f.setBoost(3.0f); doc.add(f); w.addDocument(doc); w.optimize(); w.close(); IndexSearcher s = new IndexSearcher(d); System.out.println("Range Search for: 'apple' TO 'dog'"); Hits h = s.search(new RangeQuery(new Term("words", "apple"), new Term("words", "dog"), true)); for (int i = 0; i < h.length(); i++) { System.out.println(h.score(i) + " ... " + h.doc(i).get("words")); } } }
import org.apache.lucene.index.Term; import org.apache.lucene.index.IndexReader; import org.apache.lucene.index.IndexWriter; import org.apache.lucene.search.Query; import org.apache.lucene.search.Hits; import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.store.RAMDirectory; import org.apache.lucene.analysis.SimpleAnalyzer; import org.apache.lucene.document.Document; import org.apache.lucene.document.Field; import org.apache.lucene.document.DateField; import org.apache.lucene.search.Query; import org.apache.lucene.search.TermQuery; import org.apache.lucene.search.Filter; import org.apache.lucene.search.DateFilter; import java.io.IOException; import java.util.Random; import junit.framework.TestCase; /** * A basic 'positive' Unit test class for the RangeFilter class. * * <p> * NOTE: at the moment, this class only tests for 'positive' results, * it does not verify the results to ensure their are no 'false positives', * nor does it adequately test 'negative' results. It also does not test * that garbage in results in an Exception. */ public class TestRangeFilter extends BaseTestRangeFilter { public TestRangeFilter(String name) { super(name); } public TestRangeFilter() { super(); } public void testRangeFilterId() throws IOException { IndexReader reader = IndexReader.open(index); IndexSearcher search = new IndexSearcher(reader); int medId = ((maxId - minId) / 2); String minIP = pad(minId); String maxIP = pad(maxId); String medIP = pad(medId); int numDocs = reader.numDocs(); assertEquals("num of docs", numDocs, 1+ maxId - minId); Hits result; Query q = new TermQuery(new Term("body","body")); // test id, bounded on both ends result = search.search(q,new RangeFilter("id",minIP,maxIP,T,T)); assertEquals("find all", numDocs, result.length()); result = search.search(q,new RangeFilter("id",minIP,maxIP,T,F)); assertEquals("all but last", numDocs-1, result.length()); result = search.search(q,new RangeFilter("id",minIP,maxIP,F,T)); assertEquals("all but first", numDocs-1, result.length()); result = search.search(q,new RangeFilter("id",minIP,maxIP,F,F)); assertEquals("all but ends", numDocs-2, result.length()); result = search.search(q,new RangeFilter("id",medIP,maxIP,T,T)); assertEquals("med and up", 1+ maxId-medId, result.length()); result = search.search(q,new RangeFilter("id",minIP,medIP,T,T)); assertEquals("up to med", 1+ medId-minId, result.length()); // unbounded id result = search.search(q,new RangeFilter("id",minIP,null,T,F)); assertEquals("min and up", numDocs, result.length()); result = search.search(q,new RangeFilter("id",null,maxIP,F,T)); assertEquals("max and down", numDocs, result.length()); result = search.search(q,new RangeFilter("id",minIP,null,F,F)); assertEquals("not min, but up", numDocs-1, result.length()); result = search.search(q,new RangeFilter("id",null,maxIP,F,F)); assertEquals("not max, but down", numDocs-1, result.length()); result = search.search(q,new RangeFilter("id",medIP,maxIP,T,F)); assertEquals("med and up, not max", maxId-medId, result.length()); result = search.search(q,new RangeFilter("id",minIP,medIP,F,T)); assertEquals("not min, up to med", medId-minId, result.length()); // very small sets result = search.search(q,new RangeFilter("id",minIP,minIP,F,F)); assertEquals("min,min,F,F", 0, result.length()); result = search.search(q,new RangeFilter("id",medIP,medIP,F,F)); assertEquals("med,med,F,F", 0, result.length()); result = search.search(q,new RangeFilter("id",maxIP,maxIP,F,F)); assertEquals("max,max,F,F", 0, result.length()); result = search.search(q,new RangeFilter("id",minIP,minIP,T,T)); assertEquals("min,min,T,T", 1, result.length()); result = search.search(q,new RangeFilter("id",null,minIP,F,T)); assertEquals("nul,min,F,T", 1, result.length()); result = search.search(q,new RangeFilter("id",maxIP,maxIP,T,T)); assertEquals("max,max,T,T", 1, result.length()); result = search.search(q,new RangeFilter("id",maxIP,null,T,F)); assertEquals("max,nul,T,T", 1, result.length()); result = search.search(q,new RangeFilter("id",medIP,medIP,T,T)); assertEquals("med,med,T,T", 1, result.length()); } public void testRangeFilterRand() throws IOException { IndexReader reader = IndexReader.open(index); IndexSearcher search = new IndexSearcher(reader); String minRP = pad(minR); String maxRP = pad(maxR); int numDocs = reader.numDocs(); assertEquals("num of docs", numDocs, 1+ maxId - minId); Hits result; Query q = new TermQuery(new Term("body","body")); // test extremes, bounded on both ends result = search.search(q,new RangeFilter("rand",minRP,maxRP,T,T)); assertEquals("find all", numDocs, result.length()); result = search.search(q,new RangeFilter("rand",minRP,maxRP,T,F)); assertEquals("all but biggest", numDocs-1, result.length()); result = search.search(q,new RangeFilter("rand",minRP,maxRP,F,T)); assertEquals("all but smallest", numDocs-1, result.length()); result = search.search(q,new RangeFilter("rand",minRP,maxRP,F,F)); assertEquals("all but extremes", numDocs-2, result.length()); // unbounded result = search.search(q,new RangeFilter("rand",minRP,null,T,F)); assertEquals("smallest and up", numDocs, result.length()); result = search.search(q,new RangeFilter("rand",null,maxRP,F,T)); assertEquals("biggest and down", numDocs, result.length()); result = search.search(q,new RangeFilter("rand",minRP,null,F,F)); assertEquals("not smallest, but up", numDocs-1, result.length()); result = search.search(q,new RangeFilter("rand",null,maxRP,F,F)); assertEquals("not biggest, but down", numDocs-1, result.length()); // very small sets result = search.search(q,new RangeFilter("rand",minRP,minRP,F,F)); assertEquals("min,min,F,F", 0, result.length()); result = search.search(q,new RangeFilter("rand",maxRP,maxRP,F,F)); assertEquals("max,max,F,F", 0, result.length()); result = search.search(q,new RangeFilter("rand",minRP,minRP,T,T)); assertEquals("min,min,T,T", 1, result.length()); result = search.search(q,new RangeFilter("rand",null,minRP,F,T)); assertEquals("nul,min,F,T", 1, result.length()); result = search.search(q,new RangeFilter("rand",maxRP,maxRP,T,T)); assertEquals("max,max,T,T", 1, result.length()); result = search.search(q,new RangeFilter("rand",maxRP,null,T,F)); assertEquals("max,nul,T,T", 1, result.length()); } }
--------------------------------------------------------------------- To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]