Index: modules/queries/src/test/org/apache/lucene/queries/TestValueSourceFilter.java =================================================================== --- modules/queries/src/test/org/apache/lucene/queries/TestValueSourceFilter.java (revision 0) +++ modules/queries/src/test/org/apache/lucene/queries/TestValueSourceFilter.java (revision 0) @@ -0,0 +1,133 @@ +package org.apache.lucene.queries; + +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.lucene.analysis.MockAnalyzer; +import org.apache.lucene.analysis.MockTokenizer; +import org.apache.lucene.document.Document; +import org.apache.lucene.document.TextField; +import org.apache.lucene.index.AtomicReader; +import org.apache.lucene.index.AtomicReaderContext; +import org.apache.lucene.index.RandomIndexWriter; +import org.apache.lucene.index.SlowCompositeReaderWrapper; +import org.apache.lucene.index.Term; +import org.apache.lucene.queries.function.valuesource.FloatFieldSource; +import org.apache.lucene.search.BooleanClause.Occur; +import org.apache.lucene.search.DocIdSetIterator; +import org.apache.lucene.search.Filter; +import org.apache.lucene.search.TermRangeFilter; +import org.apache.lucene.search.DocIdSet; +import org.apache.lucene.search.TermQuery; +import org.apache.lucene.search.QueryWrapperFilter; +import org.apache.lucene.store.Directory; +import org.apache.lucene.util.Bits; +import org.apache.lucene.util.FixedBitSet; +import org.apache.lucene.util.LuceneTestCase; + +import java.io.IOException; + +public class TestValueSourceFilter extends LuceneTestCase { + + private Directory directory = null; + private AtomicReader reader; + + @Override + public void setUp() throws Exception { + super.setUp(); + } + + public void createIndex(String[] values) throws Exception { + directory = newDirectory(); + RandomIndexWriter writer = + new RandomIndexWriter(random, directory, + new MockAnalyzer(random, MockTokenizer.WHITESPACE, false)); + for(String value : values) { + addDoc(writer, value); + } + reader = new SlowCompositeReaderWrapper(writer.getReader()); + writer.close(); + } + + public void destroyIndex() throws Exception { + reader.close(); + directory.close(); + } + + private void addDoc(RandomIndexWriter writer, String value) throws IOException { + Document doc = new Document(); + doc.add(newField("value", value, TextField.TYPE_STORED)); + writer.addDocument(doc); + } + + @Override + public void tearDown() throws Exception { + super.tearDown(); + } + + /** + * Get the number of documents allowed by a given filter + */ + private int getFilterDocSetSize(Filter filter) + throws Exception { + DocIdSetIterator docIdSetIterator = + filter.getDocIdSet(reader.getTopReaderContext(), reader.getLiveDocs()).iterator(); + int docSetSize = 0; + while (docIdSetIterator.nextDoc() != DocIdSetIterator.NO_MORE_DOCS) { + docSetSize++; + } + return docSetSize; + } + + /** + * Test that the filter acts as expected, filtering the correct documents + * out. + */ + public void testFieldValueFilter() throws Exception { + String[] values = { "120", "230", "340", "450" }; + createIndex(values); + + assertEquals("We should match 2 docs", 2, + getFilterDocSetSize(new ValueSourceFilter(new FloatFieldSource("value"), 300.0f))); + + assertEquals("We should match 2 docs", 0, + getFilterDocSetSize(new ValueSourceFilter(new FloatFieldSource("value"), 800.0f))); + + assertEquals("We should match 2 docs", 4, + getFilterDocSetSize(new ValueSourceFilter(new FloatFieldSource("value"), 0.0f))); + + destroyIndex(); + } + + /** + * Test that a NumberFormatException is thrown when there is trouble + * interpreting a ValueSource output as a float. + */ + public void testFieldValueFilterFloatParseFailure() throws Exception { + String[] values = { "100", "not_a_float" }; + createIndex(values); + + try { + int size = + getFilterDocSetSize(new ValueSourceFilter(new FloatFieldSource("value"), 1.0f)); + fail("Expected an Exception, but got none"); + } + catch(NumberFormatException exception) {} + + destroyIndex(); + } +} Index: modules/queries/src/java/org/apache/lucene/queries/ValueSourceFilter.java =================================================================== --- modules/queries/src/java/org/apache/lucene/queries/ValueSourceFilter.java (revision 0) +++ modules/queries/src/java/org/apache/lucene/queries/ValueSourceFilter.java (revision 0) @@ -0,0 +1,86 @@ +package org.apache.lucene.queries; + +import java.util.BitSet; +import java.io.IOException; +import java.util.Collections; + +import org.apache.lucene.index.AtomicReaderContext; +import org.apache.lucene.index.IndexReader; +import org.apache.lucene.queries.function.FunctionValues; +import org.apache.lucene.queries.function.ValueSource; +import org.apache.lucene.search.BitsFilteredDocIdSet; +import org.apache.lucene.search.Filter; +import org.apache.lucene.search.DocIdSet; +import org.apache.lucene.util.Bits; +import org.apache.lucene.util.DocIdBitSet; + +/** + * Filter out documents for which their value in an ValueSource + * is below a given minimum threshold + */ +public class ValueSourceFilter extends Filter { + + /** + * The minimum value that the given field must have for the document + * to not be filtered out. + */ + private float minimumValue; + + /** + * A valueSource by which we can check doc values against the given + * minimum threshold. + */ + private ValueSource valueSource; + + /** + * Create an ValueSourceFilter + * + * @param valueSource + * A valueSource by which we can check doc values against the given + * minimum threshold. + * + * @param minimumValue + * The minimum value that the given field must have for the document + * to not be filtered out. + */ + public ValueSourceFilter(ValueSource valueSource, + float minimumValue) { + this.minimumValue = minimumValue; + this.valueSource = valueSource; + } + + /** + * @inheritDoc + */ + public DocIdSet getDocIdSet(AtomicReaderContext readerContext, Bits acceptDocs) + throws IOException, NumberFormatException { + + BitSet bitSet = new BitSet(); + + // ValueSource.getValues throws IOException + FunctionValues funcVals = + this.valueSource.getValues(Collections.emptyMap(), readerContext); + + for(int i=0;i= this.minimumValue) { + bitSet.set(i,true); + } + } + + return BitsFilteredDocIdSet.wrap(new DocIdBitSet(bitSet), acceptDocs); + } + + + @Override + public boolean equals(Object o) { + if(o == null || getClass() != o.getClass()) { + return false; + } + return o.hashCode() == this.hashCode(); + } + + @Override + public int hashCode() { + return (int)this.minimumValue; + } +}