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,132 @@ +/** + * 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. + */ +package org.apache.lucene.queries; + +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; + + @Override + public void setUp() throws Exception { + super.setUp(); + directory = newDirectory(); + } + + @Override + public void tearDown() throws Exception { + super.tearDown(); + directory.close(); + } + + /** + * Get a reader that reads an index populated with documents that + * contain a field 'value' with the given values. + */ + private AtomicReader getReaderWithValues(String[] values) throws Exception { + RandomIndexWriter writer = + new RandomIndexWriter(random, this.directory, + new MockAnalyzer(random, MockTokenizer.WHITESPACE, false)); + for(String value : values) { + addDoc(writer, value); + } + AtomicReader reader = new SlowCompositeReaderWrapper(writer.getReader()); + writer.close(); + return reader; + } + + private void addDoc(RandomIndexWriter writer, String value) throws IOException { + Document doc = new Document(); + doc.add(newField("value", value, TextField.TYPE_STORED)); + writer.addDocument(doc); + } + + /** + * Get the number of documents allowed by a given filter + */ + private int getFilterDocSetSize(AtomicReader reader, 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 { + AtomicReader reader = + getReaderWithValues(new String[] {"120", "230", "340", "450"}); + + assertEquals("We should match 2 docs", 2, + getFilterDocSetSize(reader, new ValueSourceFilter(new FloatFieldSource("value"), 300.0f))); + + assertEquals("We should match 2 docs", 0, + getFilterDocSetSize(reader, new ValueSourceFilter(new FloatFieldSource("value"), 800.0f))); + + assertEquals("We should match 2 docs", 4, + getFilterDocSetSize(reader, new ValueSourceFilter(new FloatFieldSource("value"), 0.0f))); + + reader.close(); + } + + /** + * Test that a NumberFormatException is thrown when there is trouble + * interpreting a ValueSource output as a float. + */ + public void testFieldValueFilterFloatParseFailure() throws Exception { + AtomicReader reader = + getReaderWithValues(new String[] {"100", "not_a_float"}); + + try { + int size = + getFilterDocSetSize(reader, new ValueSourceFilter(new FloatFieldSource("value"), 1.0f)); + fail("Expected an Exception, but got none"); + } + catch(NumberFormatException exception) {} + + reader.close(); + } +} 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,81 @@ +package org.apache.lucene.queries; + +import java.io.IOException; +import java.util.Collections; +import org.apache.lucene.index.AtomicReaderContext; +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.FixedBitSet; + +/** + * Filter out documents for which their value in a 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 final float minimumValue; + + /** + * A valueSource by which we can check doc values against the given + * minimum threshold. + */ + private final 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.valueSource = valueSource; + this.minimumValue = minimumValue; + } + + /** + * @inheritDoc + */ + @Override + public DocIdSet getDocIdSet(AtomicReaderContext readerContext, Bits acceptDocs) throws IOException { + FixedBitSet bitSet = new FixedBitSet(readerContext.reader().maxDoc()); + FunctionValues funcVals = this.valueSource.getValues(Collections.emptyMap(), readerContext); + + for (int i=0; i= this.minimumValue) { + bitSet.set(i); + } + } + + return BitsFilteredDocIdSet.wrap(bitSet, acceptDocs); + } + + + @Override + public boolean equals(Object o) { + if (o == null || !(o instanceof ValueSourceFilter)) { + return false; + } + ValueSourceFilter that = (ValueSourceFilter) o; + return (this.minimumValue == that.minimumValue) && this.valueSource.equals(that.valueSource); + } + + @Override + public int hashCode() { + int h = Float.valueOf(minimumValue).hashCode(); + h ^= this.valueSource.hashCode(); + return h; + } +}