Index: lucene/facet/src/test/org/apache/lucene/facet/range/TestRangeAccumulator.java =================================================================== --- lucene/facet/src/test/org/apache/lucene/facet/range/TestRangeAccumulator.java (revision 0) +++ lucene/facet/src/test/org/apache/lucene/facet/range/TestRangeAccumulator.java (working copy) @@ -0,0 +1,150 @@ +package org.apache.lucene.facet.range; + +/* + * 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 java.util.List; + +import org.apache.lucene.document.Document; +import org.apache.lucene.document.DoubleDocValuesField; +import org.apache.lucene.document.FloatDocValuesField; +import org.apache.lucene.document.IntDocValuesField; +import org.apache.lucene.document.LongDocValuesField; +import org.apache.lucene.document.NumericDocValuesField; +import org.apache.lucene.facet.FacetTestCase; +import org.apache.lucene.facet.FacetTestUtils; +import org.apache.lucene.facet.params.FacetSearchParams; +import org.apache.lucene.facet.search.FacetResult; +import org.apache.lucene.facet.search.FacetsCollector; +import org.apache.lucene.facet.taxonomy.CategoryPath; +import org.apache.lucene.index.IndexReader; +import org.apache.lucene.index.RandomIndexWriter; +import org.apache.lucene.search.IndexSearcher; +import org.apache.lucene.search.MatchAllDocsQuery; +import org.apache.lucene.store.Directory; + +public class TestRangeAccumulator extends FacetTestCase { + + public void testBasicLong() throws Exception { + Directory d = newDirectory(); + RandomIndexWriter w = new RandomIndexWriter(random(), d); + Document doc = new Document(); + NumericDocValuesField field = new NumericDocValuesField("field", 0L); + doc.add(field); + for(long l=0;l<100;l++) { + field.setLongValue(l); + w.addDocument(doc); + } + + IndexReader r = w.getReader(); + w.close(); + + FacetSearchParams fsp = new FacetSearchParams( + new RangeFacetRequest("field", + new Range.LongRange("less than 10", 0L, true, 10L, false), + new Range.LongRange("less than or equal to 10", 0L, true, 10L, true), + new Range.LongRange("over 90", 90L, false, 100L, false), + new Range.LongRange("90 or above", 90L, true, 100L, false), + new Range.LongRange("over 1000", 1000L, false, Long.MAX_VALUE, false))); + + RangeAccumulator a = new RangeAccumulator(fsp, r); + + FacetsCollector fc = FacetsCollector.create(a); + + IndexSearcher s = newSearcher(r); + s.search(new MatchAllDocsQuery(), fc); + List result = a.accumulate(fc.getMatchingDocs()); + assertEquals(1, result.size()); + assertEquals("field (0)\n less than 10 (10)\n less than or equal to 10 (11)\n over 90 (9)\n 90 or above (10)\n over 1000 (0)\n", FacetTestUtils.toSimpleString(result.get(0))); + + r.close(); + d.close(); + } + + public void testBasicDouble() throws Exception { + Directory d = newDirectory(); + RandomIndexWriter w = new RandomIndexWriter(random(), d); + Document doc = new Document(); + DoubleDocValuesField field = new DoubleDocValuesField("field", 0.0); + doc.add(field); + for(long l=0;l<100;l++) { + field.setDoubleValue((double) l); + w.addDocument(doc); + } + + IndexReader r = w.getReader(); + w.close(); + + FacetSearchParams fsp = new FacetSearchParams( + new RangeFacetRequest("field", + new Range.DoubleRange("less than 10", 0.0, true, 10.0, false), + new Range.DoubleRange("less than or equal to 10", 0.0, true, 10.0, true), + new Range.DoubleRange("over 90", 90.0, false, 100.0, false), + new Range.DoubleRange("90 or above", 90.0, true, 100.0, false), + new Range.DoubleRange("over 1000", 1000.0, false, Double.POSITIVE_INFINITY, false))); + + RangeAccumulator a = new RangeAccumulator(fsp, r); + + FacetsCollector fc = FacetsCollector.create(a); + + IndexSearcher s = newSearcher(r); + s.search(new MatchAllDocsQuery(), fc); + List result = a.accumulate(fc.getMatchingDocs()); + assertEquals(1, result.size()); + assertEquals("field (0)\n less than 10 (10)\n less than or equal to 10 (11)\n over 90 (9)\n 90 or above (10)\n over 1000 (0)\n", FacetTestUtils.toSimpleString(result.get(0))); + + r.close(); + d.close(); + } + + public void testBasicFloat() throws Exception { + Directory d = newDirectory(); + RandomIndexWriter w = new RandomIndexWriter(random(), d); + Document doc = new Document(); + FloatDocValuesField field = new FloatDocValuesField("field", 0.0f); + doc.add(field); + for(long l=0;l<100;l++) { + field.setFloatValue((float) l); + w.addDocument(doc); + } + + IndexReader r = w.getReader(); + w.close(); + + FacetSearchParams fsp = new FacetSearchParams( + new RangeFacetRequest("field", + new Range.FloatRange("less than 10", 0.0f, true, 10.0f, false), + new Range.FloatRange("less than or equal to 10", 0.0f, true, 10.0f, true), + new Range.FloatRange("over 90", 90.0f, false, 100.0f, false), + new Range.FloatRange("90 or above", 90.0f, true, 100.0f, false), + new Range.FloatRange("over 1000", 1000.0f, false, Float.POSITIVE_INFINITY, false))); + + RangeAccumulator a = new RangeAccumulator(fsp, r); + + FacetsCollector fc = FacetsCollector.create(a); + + IndexSearcher s = newSearcher(r); + s.search(new MatchAllDocsQuery(), fc); + List result = a.accumulate(fc.getMatchingDocs()); + assertEquals(1, result.size()); + assertEquals("field (0)\n less than 10 (10)\n less than or equal to 10 (11)\n over 90 (9)\n 90 or above (10)\n over 1000 (0)\n", FacetTestUtils.toSimpleString(result.get(0))); + + r.close(); + d.close(); + } +} + Property changes on: lucene/facet/src/test/org/apache/lucene/facet/range/TestRangeAccumulator.java ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Index: lucene/facet/src/java/org/apache/lucene/facet/range/Range.java =================================================================== --- lucene/facet/src/java/org/apache/lucene/facet/range/Range.java (revision 0) +++ lucene/facet/src/java/org/apache/lucene/facet/range/Range.java (working copy) @@ -0,0 +1,134 @@ +package org.apache.lucene.facet.range; + +/* + * 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. + */ + +/** Represents a single labelled range, one facet label in + * the facets computed by {@link RangeAccumulator}. + * + * @lucene.experimental */ +public abstract class Range { + public final String label; + + protected Range(String label) { + this.label = label; + } + + public abstract boolean accept(long value); + + public final static class LongRange extends Range { + public final long minIncl; + public final long maxIncl; + + /** Create a range matching a field indexed with {@link + * NumericDocValuesField}. */ + public LongRange(String label, long min, boolean minInclusive, long max, boolean maxInclusive) { + super(label); + if (!minInclusive && min != Long.MAX_VALUE) { + min++; + } + + if (!maxInclusive && max != Long.MIN_VALUE) { + max--; + } + + this.minIncl = min; + this.maxIncl = max; + } + + @Override + public boolean accept(long value) { + return value >= minIncl && value <= maxIncl; + } + } + + public final static class DoubleRange extends Range { + public final double minIncl; + public final double maxIncl; + + public DoubleRange(String label, double min, boolean minInclusive, double max, boolean maxInclusive) { + super(label); + + // TODO: if DoubleDocValuesField used + // NumericUtils.doubleToSortableLong format (instead of + // Double.doubleToRawLongBits) we could do comparisons + // in long space + + if (Double.isNaN(min)) { + throw new IllegalArgumentException("min cannot be NaN"); + } + if (!minInclusive) { + min = Math.nextUp(min); + } + + if (Double.isNaN(max)) { + throw new IllegalArgumentException("max cannot be NaN"); + } + if (!maxInclusive) { + // Why no Math.nextDown? + max = Math.nextAfter(max, Double.NEGATIVE_INFINITY); + } + + this.minIncl = min; + this.maxIncl = max; + } + + @Override + public boolean accept(long value) { + double doubleValue = Double.longBitsToDouble(value); + return doubleValue >= minIncl && doubleValue <= maxIncl; + } + } + + public final static class FloatRange extends Range { + public final float minIncl; + public final float maxIncl; + + public FloatRange(String label, float min, boolean minInclusive, float max, boolean maxInclusive) { + super(label); + + // TODO: if FloatDocValuesField used + // NumericUtils.floatToSortableInt format (instead of + // Float.floatToRawIntBits) we could do comparisons + // in int space + + if (Float.isNaN(min)) { + throw new IllegalArgumentException("min cannot be NaN"); + } + if (!minInclusive) { + min = Math.nextUp(min); + } + + if (Float.isNaN(max)) { + throw new IllegalArgumentException("max cannot be NaN"); + } + if (!maxInclusive) { + // Why no Math.nextDown? + max = Math.nextAfter(max, Float.NEGATIVE_INFINITY); + } + + this.minIncl = min; + this.maxIncl = max; + } + + @Override + public boolean accept(long value) { + float floatValue = Float.intBitsToFloat((int) value); + return floatValue >= minIncl && floatValue <= maxIncl; + } + } +} Property changes on: lucene/facet/src/java/org/apache/lucene/facet/range/Range.java ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Index: lucene/facet/src/java/org/apache/lucene/facet/range/RangeAccumulator.java =================================================================== --- lucene/facet/src/java/org/apache/lucene/facet/range/RangeAccumulator.java (revision 0) +++ lucene/facet/src/java/org/apache/lucene/facet/range/RangeAccumulator.java (working copy) @@ -0,0 +1,173 @@ +package org.apache.lucene.facet.range; + +/* + * 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 java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.apache.lucene.facet.params.CategoryListParams; +import org.apache.lucene.facet.params.FacetSearchParams; +import org.apache.lucene.facet.search.Aggregator; +import org.apache.lucene.facet.search.FacetRequest; +import org.apache.lucene.facet.search.FacetResult; +import org.apache.lucene.facet.search.FacetResultNode; +import org.apache.lucene.facet.search.FacetsAccumulator; +import org.apache.lucene.facet.search.FacetsAggregator; +import org.apache.lucene.facet.search.FacetsCollector.MatchingDocs; +import org.apache.lucene.facet.taxonomy.CategoryPath; +import org.apache.lucene.index.IndexReader; +import org.apache.lucene.index.NumericDocValues; + +/** Uses a {@link NumericDocValues} and accumulates + * counts for provided ranges. This is dynamic (does not + * use the taxonomy index or anything from the index + * except the NumericDocValuesField). */ + +public class RangeAccumulator extends FacetsAccumulator { + + static class RangeSet { + final Range[] ranges; + final String field; + //final int[] counts; + //NumericDocValues ndv; + + public RangeSet(Range[] ranges, String field) { + this.ranges = ranges; + this.field = field; + //this.counts = new int[ranges.length]; + } + } + + final List requests = new ArrayList(); + + public RangeAccumulator(FacetSearchParams fsp, IndexReader reader) { + super(fsp, reader, null, null); + + for(FacetRequest fr : fsp.facetRequests) { + + if (!(fr instanceof RangeFacetRequest)) { + throw new IllegalArgumentException("only RangeFacetRequest is supported; got " + fsp.facetRequests.get(0).getClass()); + } + + if (fr.categoryPath.length != 1) { + throw new IllegalArgumentException("only flat (dimension only) CategoryPath is allowed"); + } + + @SuppressWarnings("rawtypes") + RangeFacetRequest rfr = (RangeFacetRequest) fr; + + requests.add(new RangeSet(rfr.ranges, rfr.categoryPath.components[0])); + } + } + + @Override + public FacetsAggregator getAggregator() { + throw new UnsupportedOperationException(); + } + + @Override + public List accumulate(List matchingDocs) throws IOException { + + // TODO: test if this is faster (in the past it was + // faster to do MachingDocs on the inside): + + /* + for(MatchingDocs hits : matchingDocs) { + for(RangeSet ranges : requests) { + ranges.ndv = hits.context.reader().getNumericDocValues(ranges.field); + } + final int length = hits.bits.length(); + int doc = 0; + while (doc < length && (doc = hits.bits.nextSetBit(doc)) != -1) { + for(RangeSet ranges : requests) { + long v = ranges.ndv.get(doc); + // TODO: use interval tree instead of linear search: + for(int i=0;i results = new ArrayList(); + for(int i=0;i nodes = new ArrayList(ranges.ranges.length); + for(int j=0;j results = new ArrayList(); + for(int i=0;i nodes = new ArrayList(ranges.ranges.length); + for(int j=0;j extends FacetRequest { + + public final Range[] ranges; + + public RangeFacetRequest(String field, T...ranges) { + super(new CategoryPath(field), 1); + this.ranges = ranges; + } + + @Override + public Aggregator createAggregator(boolean useComplements, FacetArrays arrays, TaxonomyReader taxonomy) { + throw new UnsupportedOperationException(); + } + + @Override + public double getValueOf(FacetArrays arrays, int ordinal) { + throw new UnsupportedOperationException(); + } + + @Override + public FacetArraysSource getFacetArraysSource() { + throw new UnsupportedOperationException(); + } + +} Property changes on: lucene/facet/src/java/org/apache/lucene/facet/range/RangeFacetRequest.java ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Index: lucene/facet/src/java/org/apache/lucene/facet/search/FacetsAccumulator.java =================================================================== --- lucene/facet/src/java/org/apache/lucene/facet/search/FacetsAccumulator.java (revision 1476634) +++ lucene/facet/src/java/org/apache/lucene/facet/search/FacetsAccumulator.java (working copy) @@ -58,7 +58,7 @@ * constructor. */ public FacetsAccumulator(FacetSearchParams searchParams, IndexReader indexReader, TaxonomyReader taxonomyReader) { - this(searchParams, indexReader, taxonomyReader, null); + this(searchParams, indexReader, taxonomyReader, new FacetArrays(taxonomyReader.getSize())); } /** @@ -97,9 +97,6 @@ */ public FacetsAccumulator(FacetSearchParams searchParams, IndexReader indexReader, TaxonomyReader taxonomyReader, FacetArrays facetArrays) { - if (facetArrays == null) { - facetArrays = new FacetArrays(taxonomyReader.getSize()); - } this.facetArrays = facetArrays; this.indexReader = indexReader; this.taxonomyReader = taxonomyReader; @@ -199,4 +196,7 @@ return res; } + protected boolean requiresDocScores() { + return getAggregator().requiresDocScores(); + } } Index: lucene/facet/src/java/org/apache/lucene/facet/search/FacetsCollector.java =================================================================== --- lucene/facet/src/java/org/apache/lucene/facet/search/FacetsCollector.java (revision 1476634) +++ lucene/facet/src/java/org/apache/lucene/facet/search/FacetsCollector.java (working copy) @@ -175,7 +175,7 @@ * given {@link FacetsAccumulator}. */ public static FacetsCollector create(FacetsAccumulator accumulator) { - if (accumulator.getAggregator().requiresDocScores()) { + if (accumulator.requiresDocScores()) { return new DocsAndScoresCollector(accumulator); } else { return new DocsOnlyCollector(accumulator);