org.sindice.siren.search.TestSirenNumericRangeQuery32.java Source code

Java tutorial

Introduction

Here is the source code for org.sindice.siren.search.TestSirenNumericRangeQuery32.java

Source

/**
 * Copyright (c) 2009-2011 National University of Ireland, Galway. All Rights Reserved.
 *
 * Project and contact information: http://www.siren.sindice.com/
 *
 * This file is part of the SIREn project.
 *
 * SIREn is a free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of
 * the License, or (at your option) any later version.
 *
 * SIREn is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public
 * License along with SIREn. If not, see <http://www.gnu.org/licenses/>.
 */
/**
 * @project siren-core
 * @author Renaud Delbru [ 29 Sep 2011 ]
 * @link http://renaud.delbru.fr/
 */
package org.sindice.siren.search;

import org.apache.lucene.analysis.WhitespaceAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.NumericField.DataType;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.RandomIndexWriter;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.FilteredTermEnum;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.QueryUtils;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.Sort;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.store.Directory;
import org.apache.lucene.util.LuceneTestCase;
import org.apache.lucene.util.NumericUtils;
import org.apache.lucene.util._TestUtil;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.sindice.siren.analysis.FloatNumericAnalyzer;
import org.sindice.siren.analysis.IntNumericAnalyzer;
import org.sindice.siren.analysis.TupleAnalyzer;
import org.sindice.siren.util.XSDDatatype;

public class TestSirenNumericRangeQuery32 extends LuceneTestCase {

    // distance of entries
    private static final int distance = 6666;
    // shift the starting of the values to the left, to also have negative values:
    private static final int startOffset = -1 << 15;
    // number of docs to generate for testing
    private static final int noDocs = 10000 * RANDOM_MULTIPLIER;

    private static Directory directory = null;
    private static IndexReader reader = null;
    private static IndexSearcher searcher = null;

    @BeforeClass
    public static void beforeClass() throws Exception {
        directory = newDirectory();

        final TupleAnalyzer analyzer = new TupleAnalyzer(TEST_VERSION_CURRENT,
                new WhitespaceAnalyzer(TEST_VERSION_CURRENT), new WhitespaceAnalyzer(TEST_VERSION_CURRENT));
        analyzer.registerLiteralAnalyzer((XSDDatatype.XSD_INT + "8").toCharArray(), new IntNumericAnalyzer(8));
        analyzer.registerLiteralAnalyzer((XSDDatatype.XSD_INT + "4").toCharArray(), new IntNumericAnalyzer(4));
        analyzer.registerLiteralAnalyzer((XSDDatatype.XSD_INT + "2").toCharArray(), new IntNumericAnalyzer(2));
        analyzer.registerLiteralAnalyzer((XSDDatatype.XSD_FLOAT + "8").toCharArray(), new FloatNumericAnalyzer(8));
        analyzer.registerLiteralAnalyzer((XSDDatatype.XSD_FLOAT + "4").toCharArray(), new FloatNumericAnalyzer(4));
        analyzer.registerLiteralAnalyzer((XSDDatatype.XSD_FLOAT + "2").toCharArray(), new FloatNumericAnalyzer(2));
        analyzer.registerLiteralAnalyzer((XSDDatatype.XSD_INT + Integer.MAX_VALUE).toCharArray(),
                new IntNumericAnalyzer(Integer.MAX_VALUE));

        final RandomIndexWriter writer = new RandomIndexWriter(random, directory,
                newIndexWriterConfig(TEST_VERSION_CURRENT, analyzer)
                        .setMaxBufferedDocs(_TestUtil.nextInt(random, 100, 1000))
                        .setMergePolicy(newLogMergePolicy()));

        // Add a series of noDocs docs with increasing int values
        for (int l = 0; l < noDocs; l++) {
            final Document doc = new Document();

            int val = distance * l + startOffset;

            // add fields, that have a distance to test general functionality
            doc.add(newField("field8", getTriple(val, XSDDatatype.XSD_INT + "8"), Field.Store.YES,
                    Field.Index.ANALYZED));
            doc.add(newField("field4", getTriple(val, XSDDatatype.XSD_INT + "4"), Field.Store.YES,
                    Field.Index.ANALYZED));
            doc.add(newField("field2", getTriple(val, XSDDatatype.XSD_INT + "2"), Field.Store.YES,
                    Field.Index.ANALYZED));
            doc.add(newField("field" + Integer.MAX_VALUE, getTriple(val, XSDDatatype.XSD_INT + Integer.MAX_VALUE),
                    Field.Store.YES, Field.Index.ANALYZED));

            // add ascending fields with a distance of 1, beginning at -noDocs/2 to
            // test the correct splitting of range and inclusive/exclusive
            val = l - (noDocs / 2);

            doc.add(newField("ascfield8", getTriple(val, XSDDatatype.XSD_INT + "8"), Field.Store.YES,
                    Field.Index.ANALYZED));
            doc.add(newField("ascfield4", getTriple(val, XSDDatatype.XSD_INT + "4"), Field.Store.YES,
                    Field.Index.ANALYZED));
            doc.add(newField("ascfield2", getTriple(val, XSDDatatype.XSD_INT + "2"), Field.Store.YES,
                    Field.Index.ANALYZED));

            doc.add(newField("float8", getTriple(val, XSDDatatype.XSD_FLOAT + "8"), Field.Store.YES,
                    Field.Index.ANALYZED));
            doc.add(newField("float4", getTriple(val, XSDDatatype.XSD_FLOAT + "4"), Field.Store.YES,
                    Field.Index.ANALYZED));
            doc.add(newField("float2", getTriple(val, XSDDatatype.XSD_FLOAT + "2"), Field.Store.YES,
                    Field.Index.ANALYZED));

            writer.addDocument(doc);
        }

        reader = writer.getReader();
        searcher = newSearcher(reader);
        writer.close();
    }

    @AfterClass
    public static void afterClass() throws Exception {
        searcher.close();
        searcher = null;
        reader.close();
        reader = null;
        directory.close();
        directory = null;
    }

    @Override
    public void setUp() throws Exception {
        super.setUp();
        // Remove maximum clause limit for the tests
        SirenBooleanQuery.setMaxClauseCount(Integer.MAX_VALUE);
    }

    private static String getTriple(final int val, final String datatypeURI) {
        return "<http://fake.subject> <http://fake.predicate/" + val + "> \"" + val + "\"^^<" + datatypeURI
                + "> .\n";
    }

    private static String getLiteralValue(final String triple) {
        final int firstColon = triple.indexOf('"');
        final int secondColon = triple.indexOf('"', firstColon + 1);
        return triple.substring(firstColon + 1, secondColon);
    }

    /** test for both constant score and boolean query, the other tests only use the constant score mode */
    private void testRange(final int precisionStep) throws Exception {
        final String field = "field" + precisionStep;
        final int count = 3000;
        final int lower = (distance * 3 / 2) + startOffset, upper = lower + count * distance + (distance / 3);
        final SirenNumericRangeQuery<Integer> q = SirenNumericRangeQuery.newIntRange(field, precisionStep, lower,
                upper, true, true);
        final SirenCellQuery cq = new SirenCellQuery(q);
        cq.setConstraint(2);

        TopDocs topDocs;
        String type;
        q.clearTotalNumberOfTerms();

        type = " (constant score boolean rewrite)";
        q.setRewriteMethod(SirenMultiTermQuery.CONSTANT_SCORE_BOOLEAN_QUERY_REWRITE);
        topDocs = searcher.search(cq, null, noDocs, Sort.INDEXORDER);

        final ScoreDoc[] sd = topDocs.scoreDocs;
        assertNotNull(sd);
        assertEquals("Score doc count" + type, count, sd.length);
        Document doc = searcher.doc(sd[0].doc);
        assertEquals("First doc" + type, 2 * distance + startOffset,
                Integer.parseInt(getLiteralValue(doc.get(field))));
        doc = searcher.doc(sd[sd.length - 1].doc);
        assertEquals("Last doc" + type, (1 + count) * distance + startOffset,
                Integer.parseInt(getLiteralValue(doc.get(field))));
    }

    @Test
    public void testRange_8bit() throws Exception {
        this.testRange(8);
    }

    @Test
    public void testRange_4bit() throws Exception {
        this.testRange(4);
    }

    @Test
    public void testRange_2bit() throws Exception {
        this.testRange(2);
    }

    @Test
    public void testInverseRange() throws Exception {
        SirenNumericRangeQuery<Integer> q = SirenNumericRangeQuery.newIntRange("field8", 8, 1000, -1000, true,
                true);
        q.clearTotalNumberOfTerms();
        q.setRewriteMethod(SirenMultiTermQuery.CONSTANT_SCORE_BOOLEAN_QUERY_REWRITE);
        TopDocs topDocs = searcher.search(q, null, noDocs, Sort.INDEXORDER);
        assertEquals("A inverse range should return the EMPTY_DOCIDSET instance", 0, topDocs.totalHits);

        q = SirenNumericRangeQuery.newIntRange("field8", 8, Integer.MAX_VALUE, null, false, false);
        q.clearTotalNumberOfTerms();
        q.setRewriteMethod(SirenMultiTermQuery.CONSTANT_SCORE_BOOLEAN_QUERY_REWRITE);
        topDocs = searcher.search(q, null, noDocs, Sort.INDEXORDER);
        assertEquals("A exclusive range starting with Integer.MAX_VALUE should return the EMPTY_DOCIDSET instance",
                0, topDocs.totalHits);

        q = SirenNumericRangeQuery.newIntRange("field8", 8, null, Integer.MIN_VALUE, false, false);
        q.clearTotalNumberOfTerms();
        q.setRewriteMethod(SirenMultiTermQuery.CONSTANT_SCORE_BOOLEAN_QUERY_REWRITE);
        topDocs = searcher.search(q, null, noDocs, Sort.INDEXORDER);
        assertEquals("A exclusive range ending with Integer.MIN_VALUE should return the EMPTY_DOCIDSET instance", 0,
                topDocs.totalHits);
    }

    @Test
    public void testOneMatchQuery() throws Exception {
        final SirenNumericRangeQuery<Integer> q = SirenNumericRangeQuery.newIntRange("ascfield8", 8, 1000, 1000,
                true, true);
        assertSame(SirenMultiTermQuery.CONSTANT_SCORE_BOOLEAN_QUERY_REWRITE, q.getRewriteMethod());
        final TopDocs topDocs = searcher.search(q, noDocs);
        final ScoreDoc[] sd = topDocs.scoreDocs;
        assertNotNull(sd);
        assertEquals("Score doc count", 1, sd.length);
    }

    private void testLeftOpenRange(final int precisionStep) throws Exception {
        final String field = "field" + precisionStep;
        final int count = 3000;
        final int upper = (count - 1) * distance + (distance / 3) + startOffset;
        SirenNumericRangeQuery<Integer> q = SirenNumericRangeQuery.newIntRange(field, precisionStep, null, upper,
                true, true);
        SirenCellQuery cq = new SirenCellQuery(q);
        cq.setConstraint(2);

        TopDocs topDocs = searcher.search(cq, null, noDocs, Sort.INDEXORDER);
        if (VERBOSE)
            System.out.println("Found " + q.getTotalNumberOfTerms()
                    + " distinct terms in left open range for field '" + field + "'.");
        ScoreDoc[] sd = topDocs.scoreDocs;
        assertNotNull(sd);
        assertEquals("Score doc count", count, sd.length);
        Document doc = searcher.doc(sd[0].doc);
        assertEquals("First doc", startOffset, Integer.parseInt(getLiteralValue(doc.get(field))));
        doc = searcher.doc(sd[sd.length - 1].doc);
        assertEquals("Last doc", (count - 1) * distance + startOffset,
                Integer.parseInt(getLiteralValue(doc.get(field))));

        q = SirenNumericRangeQuery.newIntRange(field, precisionStep, null, upper, false, true);
        cq = new SirenCellQuery(q);
        cq.setConstraint(2);
        topDocs = searcher.search(cq, null, noDocs, Sort.INDEXORDER);
        sd = topDocs.scoreDocs;
        assertNotNull(sd);
        assertEquals("Score doc count", count, sd.length);
        doc = searcher.doc(sd[0].doc);
        assertEquals("First doc", startOffset, Integer.parseInt(getLiteralValue(doc.get(field))));
        doc = searcher.doc(sd[sd.length - 1].doc);
        assertEquals("Last doc", (count - 1) * distance + startOffset,
                Integer.parseInt(getLiteralValue(doc.get(field))));
    }

    @Test
    public void testLeftOpenRange_8bit() throws Exception {
        this.testLeftOpenRange(8);
    }

    @Test
    public void testLeftOpenRange_4bit() throws Exception {
        this.testLeftOpenRange(4);
    }

    @Test
    public void testLeftOpenRange_2bit() throws Exception {
        this.testLeftOpenRange(2);
    }

    private void testRightOpenRange(final int precisionStep) throws Exception {
        final String field = "field" + precisionStep;
        final int count = 3000;
        final int lower = (count - 1) * distance + (distance / 3) + startOffset;
        SirenNumericRangeQuery<Integer> q = SirenNumericRangeQuery.newIntRange(field, precisionStep, lower, null,
                true, true);
        SirenCellQuery cq = new SirenCellQuery(q);
        cq.setConstraint(2);

        TopDocs topDocs = searcher.search(cq, null, noDocs, Sort.INDEXORDER);
        if (VERBOSE)
            System.out.println("Found " + q.getTotalNumberOfTerms()
                    + " distinct terms in right open range for field '" + field + "'.");
        ScoreDoc[] sd = topDocs.scoreDocs;
        assertNotNull(sd);
        assertEquals("Score doc count", noDocs - count, sd.length);
        Document doc = searcher.doc(sd[0].doc);
        assertEquals("First doc", count * distance + startOffset,
                Integer.parseInt(getLiteralValue(doc.get(field))));
        doc = searcher.doc(sd[sd.length - 1].doc);
        assertEquals("Last doc", (noDocs - 1) * distance + startOffset,
                Integer.parseInt(getLiteralValue(doc.get(field))));

        q = SirenNumericRangeQuery.newIntRange(field, precisionStep, lower, null, true, false);
        cq = new SirenCellQuery(q);
        cq.setConstraint(2);
        topDocs = searcher.search(cq, null, noDocs, Sort.INDEXORDER);
        sd = topDocs.scoreDocs;
        assertNotNull(sd);
        assertEquals("Score doc count", noDocs - count, sd.length);
        doc = searcher.doc(sd[0].doc);
        assertEquals("First doc", count * distance + startOffset,
                Integer.parseInt(getLiteralValue(doc.get(field))));
        doc = searcher.doc(sd[sd.length - 1].doc);
        assertEquals("Last doc", (noDocs - 1) * distance + startOffset,
                Integer.parseInt(getLiteralValue(doc.get(field))));
    }

    @Test
    public void testRightOpenRange_8bit() throws Exception {
        this.testRightOpenRange(8);
    }

    @Test
    public void testRightOpenRange_4bit() throws Exception {
        this.testRightOpenRange(4);
    }

    @Test
    public void testRightOpenRange_2bit() throws Exception {
        this.testRightOpenRange(2);
    }

    private void testRandomTrieAndClassicRangeQuery(final int precisionStep) throws Exception {
        final String field = "field" + precisionStep;
        int termCountT = 0, termCountC = 0;
        final int num = 10 * RANDOM_MULTIPLIER;
        for (int i = 0; i < num; i++) {
            int lower = (int) (random.nextDouble() * noDocs * distance) + startOffset;
            int upper = (int) (random.nextDouble() * noDocs * distance) + startOffset;
            if (lower > upper) {
                final int a = lower;
                lower = upper;
                upper = a;
            }
            final String prefix = DataType.INT.name() + precisionStep;
            // test inclusive range
            SirenNumericRangeQuery<Integer> tq = SirenNumericRangeQuery.newIntRange(field, precisionStep, lower,
                    upper, true, true);
            SirenTermRangeQuery cq = new SirenTermRangeQuery(field, prefix + NumericUtils.intToPrefixCoded(lower),
                    prefix + NumericUtils.intToPrefixCoded(upper), true, true);
            TopDocs tTopDocs = searcher.search(tq, 1);
            TopDocs cTopDocs = searcher.search(cq, 1);
            assertEquals("Returned count for SirenNumericRangeQuery and SirenTermRangeQuery must be equal",
                    cTopDocs.totalHits, tTopDocs.totalHits);
            termCountT += tq.getTotalNumberOfTerms();
            termCountC += cq.getTotalNumberOfTerms();
            // test exclusive range
            tq = SirenNumericRangeQuery.newIntRange(field, precisionStep, lower, upper, false, false);
            cq = new SirenTermRangeQuery(field, prefix + NumericUtils.intToPrefixCoded(lower),
                    prefix + NumericUtils.intToPrefixCoded(upper), false, false);
            tTopDocs = searcher.search(tq, 1);
            cTopDocs = searcher.search(cq, 1);
            assertEquals("Returned count for SirenNumericRangeQuery and SirenTermRangeQuery must be equal",
                    cTopDocs.totalHits, tTopDocs.totalHits);
            termCountT += tq.getTotalNumberOfTerms();
            termCountC += cq.getTotalNumberOfTerms();
            // test left exclusive range
            tq = SirenNumericRangeQuery.newIntRange(field, precisionStep, lower, upper, false, true);
            cq = new SirenTermRangeQuery(field, prefix + NumericUtils.intToPrefixCoded(lower),
                    prefix + NumericUtils.intToPrefixCoded(upper), false, true);
            tTopDocs = searcher.search(tq, 1);
            cTopDocs = searcher.search(cq, 1);
            assertEquals("Returned count for SirenNumericRangeQuery and SirenTermRangeQuery must be equal",
                    cTopDocs.totalHits, tTopDocs.totalHits);
            termCountT += tq.getTotalNumberOfTerms();
            termCountC += cq.getTotalNumberOfTerms();
            // test right exclusive range
            tq = SirenNumericRangeQuery.newIntRange(field, precisionStep, lower, upper, true, false);
            cq = new SirenTermRangeQuery(field, prefix + NumericUtils.intToPrefixCoded(lower),
                    prefix + NumericUtils.intToPrefixCoded(upper), true, false);
            tTopDocs = searcher.search(tq, 1);
            cTopDocs = searcher.search(cq, 1);
            assertEquals("Returned count for SirenNumericRangeQuery and SirenTermRangeQuery must be equal",
                    cTopDocs.totalHits, tTopDocs.totalHits);
            termCountT += tq.getTotalNumberOfTerms();
            termCountC += cq.getTotalNumberOfTerms();
        }
        if (precisionStep == Integer.MAX_VALUE && searcher.getIndexReader().getSequentialSubReaders().length == 1) {
            assertEquals("Total number of terms should be equal for unlimited precStep", termCountT, termCountC);
        } else if (VERBOSE) {
            System.out.println("Average number of terms during random search on '" + field + "':");
            System.out.println(" Trie query: " + (((double) termCountT) / (num * 4)));
            System.out.println(" Classical query: " + (((double) termCountC) / (num * 4)));
        }
    }

    @Test
    public void testRandomTrieAndClassicRangeQuery_8bit() throws Exception {
        this.testRandomTrieAndClassicRangeQuery(8);
    }

    @Test
    public void testRandomTrieAndClassicRangeQuery_4bit() throws Exception {
        this.testRandomTrieAndClassicRangeQuery(4);
    }

    @Test
    public void testRandomTrieAndClassicRangeQuery_2bit() throws Exception {
        this.testRandomTrieAndClassicRangeQuery(2);
    }

    @Test
    public void testRandomTrieAndClassicRangeQuery_NoTrie() throws Exception {
        this.testRandomTrieAndClassicRangeQuery(Integer.MAX_VALUE);
    }

    private void testRangeSplit(final int precisionStep) throws Exception {
        final String field = "ascfield" + precisionStep;
        // 10 random tests
        final int num = 10 * RANDOM_MULTIPLIER;
        for (int i = 0; i < num; i++) {
            int lower = (int) (random.nextDouble() * noDocs - noDocs / 2);
            int upper = (int) (random.nextDouble() * noDocs - noDocs / 2);
            if (lower > upper) {
                final int a = lower;
                lower = upper;
                upper = a;
            }
            // test inclusive range
            Query tq = SirenNumericRangeQuery.newIntRange(field, precisionStep, lower, upper, true, true);
            TopDocs tTopDocs = searcher.search(tq, 1);
            assertEquals("Returned count of range query must be equal to inclusive range length", upper - lower + 1,
                    tTopDocs.totalHits);
            // test exclusive range
            tq = SirenNumericRangeQuery.newIntRange(field, precisionStep, lower, upper, false, false);
            tTopDocs = searcher.search(tq, 1);
            assertEquals("Returned count of range query must be equal to exclusive range length",
                    Math.max(upper - lower - 1, 0), tTopDocs.totalHits);
            // test left exclusive range
            tq = SirenNumericRangeQuery.newIntRange(field, precisionStep, lower, upper, false, true);
            tTopDocs = searcher.search(tq, 1);
            assertEquals("Returned count of range query must be equal to half exclusive range length",
                    upper - lower, tTopDocs.totalHits);
            // test right exclusive range
            tq = SirenNumericRangeQuery.newIntRange(field, precisionStep, lower, upper, true, false);
            tTopDocs = searcher.search(tq, 1);
            assertEquals("Returned count of range query must be equal to half exclusive range length",
                    upper - lower, tTopDocs.totalHits);
        }
    }

    @Test
    public void testRangeSplit_8bit() throws Exception {
        this.testRangeSplit(8);
    }

    @Test
    public void testRangeSplit_4bit() throws Exception {
        this.testRangeSplit(4);
    }

    @Test
    public void testRangeSplit_2bit() throws Exception {
        this.testRangeSplit(2);
    }

    /** we fake a float test using int2float conversion of NumericUtils */
    private void testFloatRange(final int precisionStep) throws Exception {
        final String field = "float" + precisionStep;
        final float lower = -1000, upper = +2000;

        //    final Query tq=SirenNumericRangeQuery.newFloatRange(field, precisionStep,
        //      NumericUtils.sortableIntToFloat(lower), NumericUtils.sortableIntToFloat(upper), true, true);

        /*
         * Original Lucene test was faking a float using the NumericUtils.sortableIntToFloat method.
         * Since in Siren we index also the datatype, we cannot do that: using a float query to search
         * for a value indexed with XSD_INT datatype. 
         */
        final Query tq = SirenNumericRangeQuery.newFloatRange(field, precisionStep, lower, upper, true, true);
        final TopDocs tTopDocs = searcher.search(tq, 1);
        assertEquals("Returned count of range query must be equal to inclusive range length", upper - lower + 1,
                tTopDocs.totalHits);
    }

    @Test
    public void testFloatRange_8bit() throws Exception {
        this.testFloatRange(8);
    }

    @Test
    public void testFloatRange_4bit() throws Exception {
        this.testFloatRange(4);
    }

    @Test
    public void testFloatRange_2bit() throws Exception {
        this.testFloatRange(2);
    }

    @Test
    public void testEqualsAndHash() throws Exception {
        QueryUtils.checkHashEquals(SirenNumericRangeQuery.newIntRange("test1", 4, 10, 20, true, true));
        QueryUtils.checkHashEquals(SirenNumericRangeQuery.newIntRange("test2", 4, 10, 20, false, true));
        QueryUtils.checkHashEquals(SirenNumericRangeQuery.newIntRange("test3", 4, 10, 20, true, false));
        QueryUtils.checkHashEquals(SirenNumericRangeQuery.newIntRange("test4", 4, 10, 20, false, false));
        QueryUtils.checkHashEquals(SirenNumericRangeQuery.newIntRange("test5", 4, 10, null, true, true));
        QueryUtils.checkHashEquals(SirenNumericRangeQuery.newIntRange("test6", 4, null, 20, true, true));
        QueryUtils.checkHashEquals(SirenNumericRangeQuery.newIntRange("test7", 4, null, null, true, true));
        QueryUtils.checkEqual(SirenNumericRangeQuery.newIntRange("test8", 4, 10, 20, true, true),
                SirenNumericRangeQuery.newIntRange("test8", 4, 10, 20, true, true));
        QueryUtils.checkUnequal(SirenNumericRangeQuery.newIntRange("test9", 4, 10, 20, true, true),
                SirenNumericRangeQuery.newIntRange("test9", 8, 10, 20, true, true));
        QueryUtils.checkUnequal(SirenNumericRangeQuery.newIntRange("test10a", 4, 10, 20, true, true),
                SirenNumericRangeQuery.newIntRange("test10b", 4, 10, 20, true, true));
        QueryUtils.checkUnequal(SirenNumericRangeQuery.newIntRange("test11", 4, 10, 20, true, true),
                SirenNumericRangeQuery.newIntRange("test11", 4, 20, 10, true, true));
        QueryUtils.checkUnequal(SirenNumericRangeQuery.newIntRange("test12", 4, 10, 20, true, true),
                SirenNumericRangeQuery.newIntRange("test12", 4, 10, 20, false, true));
        QueryUtils.checkUnequal(SirenNumericRangeQuery.newIntRange("test13", 4, 10, 20, true, true),
                SirenNumericRangeQuery.newFloatRange("test13", 4, 10f, 20f, true, true));
        // the following produces a hash collision, because Long and Integer have the same hashcode, so only test equality:
        final Query q1 = SirenNumericRangeQuery.newIntRange("test14", 4, 10, 20, true, true);
        final Query q2 = SirenNumericRangeQuery.newLongRange("test14", 4, 10L, 20L, true, true);
        assertFalse(q1.equals(q2));
        assertFalse(q2.equals(q1));
    }

    private void testEnum(final int lower, final int upper) throws Exception {
        final SirenNumericRangeQuery<Integer> q = SirenNumericRangeQuery.newIntRange("field4", 4, lower, upper,
                true, true);
        final FilteredTermEnum termEnum = q.getEnum(searcher.getIndexReader());
        try {
            int count = 0;
            do {
                final Term t = termEnum.term();
                if (t != null) {
                    // without the datatype and precision step prefix
                    final int val = NumericUtils
                            .prefixCodedToInt(t.text().substring(DataType.INT.name().length() + 1));
                    assertTrue("value not in bounds", val >= lower && val <= upper);
                    count++;
                } else
                    break;
            } while (termEnum.next());
            assertFalse(termEnum.next());
            if (VERBOSE)
                System.out.println("TermEnum on 'field4' for range [" + lower + "," + upper + "] contained " + count
                        + " terms.");
        } finally {
            termEnum.close();
        }
    }

    @Test
    public void testEnum() throws Exception {
        final int count = 3000;
        int lower = (distance * 3 / 2) + startOffset, upper = lower + count * distance + (distance / 3);
        // test enum with values
        this.testEnum(lower, upper);
        // test empty enum
        this.testEnum(upper, lower);
        // test empty enum outside of bounds
        lower = distance * noDocs + startOffset;
        upper = 2 * lower;
        this.testEnum(lower, upper);
    }

}