LUCENE-10245: Addition of MultiDoubleValues(Source) and MultiLongValues(Source) along with faceting capabilities (#543)

This commit is contained in:
Greg Miller 2022-01-10 13:48:36 -08:00 committed by GitHub
parent bff930c1bf
commit 82703757fe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 1895 additions and 46 deletions

View File

@ -104,6 +104,12 @@ New Features
* LUCENE-10335: Add ModuleResourceLoader as complement to ClasspathResourceLoader. * LUCENE-10335: Add ModuleResourceLoader as complement to ClasspathResourceLoader.
(Uwe Schindler) (Uwe Schindler)
* LUCENE-10245: MultiDoubleValues(Source) and MultiLongValues(Source) were added as multi-valued
versions of DoubleValues(Source) and LongValues(Source) to the facets module. LongValueFacetCounts,
LongRangeFacetCounts and DoubleRangeFacetCounts were augmented to support these new multi-valued
abstractions. DoubleRange and LongRange also support creating queries from these multi-valued
sources. (Greg Miller)
* LUCENE-10250: Add support for arbitrary length hierarchical SSDV facets. (Marc D'mello) * LUCENE-10250: Add support for arbitrary length hierarchical SSDV facets. (Marc D'mello)
Improvements Improvements

View File

@ -118,6 +118,8 @@ public class DistanceFacetsExample implements Closeable {
writer.close(); writer.close();
} }
// TODO: Would be nice to augment this example with documents containing multiple "locations",
// adding the ability to compute distance facets for the multi-valued case (see LUCENE-10245)
private DoubleValuesSource getDistanceValueSource() { private DoubleValuesSource getDistanceValueSource() {
Expression distance; Expression distance;
try { try {

View File

@ -70,7 +70,7 @@ public class LongValueFacetCounts extends Facets {
* been indexed). * been indexed).
*/ */
public LongValueFacetCounts(String field, FacetsCollector hits) throws IOException { public LongValueFacetCounts(String field, FacetsCollector hits) throws IOException {
this(field, null, hits); this(field, (LongValuesSource) null, hits);
} }
/** /**
@ -87,12 +87,32 @@ public class LongValueFacetCounts extends Facets {
} }
} }
/**
* Create {@code LongValueFacetCounts}, using the provided {@link MultiLongValuesSource} if
* non-null. If {@code valuesSource} is null, doc values from the provided {@code field} will be
* used.
*/
public LongValueFacetCounts(
String field, MultiLongValuesSource valuesSource, FacetsCollector hits) throws IOException {
this.field = field;
if (valuesSource != null) {
LongValuesSource singleValues = MultiLongValuesSource.unwrapSingleton(valuesSource);
if (singleValues != null) {
count(singleValues, hits.getMatchingDocs());
} else {
count(valuesSource, hits.getMatchingDocs());
}
} else {
count(field, hits.getMatchingDocs());
}
}
/** /**
* Counts all facet values for this reader. This produces the same result as computing facets on a * Counts all facet values for this reader. This produces the same result as computing facets on a
* {@link org.apache.lucene.search.MatchAllDocsQuery}, but is more efficient. * {@link org.apache.lucene.search.MatchAllDocsQuery}, but is more efficient.
*/ */
public LongValueFacetCounts(String field, IndexReader reader) throws IOException { public LongValueFacetCounts(String field, IndexReader reader) throws IOException {
this(field, null, reader); this(field, (LongValuesSource) null, reader);
} }
/** /**
@ -111,6 +131,27 @@ public class LongValueFacetCounts extends Facets {
} }
} }
/**
* Counts all facet values for the provided {@link MultiLongValuesSource} if non-null. If {@code
* valueSource} is null, doc values from the provided {@code field} will be used. This produces
* the same result as computing facets on a {@link org.apache.lucene.search.MatchAllDocsQuery},
* but is more efficient.
*/
public LongValueFacetCounts(String field, MultiLongValuesSource valuesSource, IndexReader reader)
throws IOException {
this.field = field;
if (valuesSource != null) {
LongValuesSource singleValued = MultiLongValuesSource.unwrapSingleton(valuesSource);
if (singleValued != null) {
countAll(reader, singleValued);
} else {
countAll(reader, valuesSource);
}
} else {
countAll(reader, field);
}
}
/** Counts from the provided valueSource. */ /** Counts from the provided valueSource. */
private void count(LongValuesSource valueSource, List<MatchingDocs> matchingDocs) private void count(LongValuesSource valueSource, List<MatchingDocs> matchingDocs)
throws IOException { throws IOException {
@ -137,6 +178,37 @@ public class LongValueFacetCounts extends Facets {
} }
} }
/** Counts from the provided valuesSource. */
private void count(MultiLongValuesSource valuesSource, List<MatchingDocs> matchingDocs)
throws IOException {
for (MatchingDocs hits : matchingDocs) {
MultiLongValues multiValues = valuesSource.getValues(hits.context);
DocIdSetIterator docs = hits.bits.iterator();
for (int doc = docs.nextDoc(); doc != DocIdSetIterator.NO_MORE_DOCS; ) {
// Skip missing docs:
if (multiValues.advanceExact(doc)) {
long limit = multiValues.getValueCount();
if (limit > 0) {
totCount++;
}
long previousValue = 0;
for (int i = 0; i < limit; i++) {
long value = multiValues.nextValue();
// do not increment the count for duplicate values
if (i == 0 || value != previousValue) {
increment(value);
previousValue = value;
}
}
}
doc = docs.nextDoc();
}
}
}
/** Counts from the field's indexed doc values. */ /** Counts from the field's indexed doc values. */
private void count(String field, List<MatchingDocs> matchingDocs) throws IOException { private void count(String field, List<MatchingDocs> matchingDocs) throws IOException {
for (MatchingDocs hits : matchingDocs) { for (MatchingDocs hits : matchingDocs) {
@ -194,6 +266,34 @@ public class LongValueFacetCounts extends Facets {
} }
} }
/** Count everything in the provided valueSource. */
private void countAll(IndexReader reader, MultiLongValuesSource valueSource) throws IOException {
for (LeafReaderContext context : reader.leaves()) {
MultiLongValues multiValues = valueSource.getValues(context);
int maxDoc = context.reader().maxDoc();
for (int doc = 0; doc < maxDoc; doc++) {
// Skip missing docs:
if (multiValues.advanceExact(doc)) {
long limit = multiValues.getValueCount();
if (limit > 0) {
totCount++;
}
long previousValue = 0;
for (int i = 0; i < limit; i++) {
long value = multiValues.nextValue();
// do not increment the count for duplicate values
if (i == 0 || value != previousValue) {
increment(value);
previousValue = value;
}
}
}
}
}
}
/** Count everything in the specified field. */ /** Count everything in the specified field. */
private void countAll(IndexReader reader, String field) throws IOException { private void countAll(IndexReader reader, String field) throws IOException {

View File

@ -0,0 +1,50 @@
/*
* 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.facet;
import java.io.IOException;
import org.apache.lucene.search.DoubleValues;
/**
* Per-segment, per-document double values, which can be calculated at search-time. Documents may
* produce multiple values. See also {@link DoubleValues} for a single-valued version.
*
* <p>Currently meant only for use within the faceting module. Could be further generalized and made
* available for more use-cases outside faceting if there is a desire to do so.
*
* @lucene.experimental
*/
public abstract class MultiDoubleValues {
/** Instantiates a new MultiDoubleValues */
public MultiDoubleValues() {}
/**
* Retrieves the number of values for the current document. This must always be greater than zero.
* It is illegal to call this method after {@link #advanceExact(int)} returned {@code false}.
*/
public abstract long getValueCount();
/**
* Iterates to the next value in the current document. Do not call this more than {@link
* #getValueCount} times for the document.
*/
public abstract double nextValue() throws IOException;
/** Advance to exactly {@code doc} and return whether {@code doc} has a value. */
public abstract boolean advanceExact(int doc) throws IOException;
}

View File

@ -0,0 +1,278 @@
/*
* 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.facet;
import java.io.IOException;
import java.util.Objects;
import java.util.function.LongToDoubleFunction;
import org.apache.lucene.index.DocValues;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.index.SortedNumericDocValues;
import org.apache.lucene.search.DoubleValues;
import org.apache.lucene.search.DoubleValuesSource;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.SegmentCacheable;
/**
* Base class for producing {@link MultiDoubleValues}. See also {@link DoubleValuesSource} for a
* single-valued version.
*
* <p>MultiDoubleValuesSource objects for NumericDocValues/SortedNumericDocValues fields can be
* obtained by calling {@link #fromFloatField(String)}, {@link #fromDoubleField(String)}, {@link
* #fromIntField(String)}, or {@link #fromLongField(String)}. If custom long-to-double logic is
* required, {@link #fromField(String, LongToDoubleFunction)} can be used. This is valid for both
* multi-valued and single-valued fields.
*
* <p>To obtain a MultiDoubleValuesSource from an existing {@link DoubleValuesSource}, see {@link
* #fromSingleValued(DoubleValuesSource)}. Instances created in this way can be "unwrapped" using
* {@link #unwrapSingleton(MultiDoubleValuesSource)} if necessary. Note that scores are never
* provided to the underlying {@code DoubleValuesSource}. {@link
* DoubleValuesSource#rewrite(IndexSearcher)} will also never be called. The user should be aware of
* this if using a {@code DoubleValuesSource} that relies on rewriting or scores. The faceting
* use-cases don't call rewrite or provide scores, which is why this simplification was made.
*
* <p>Currently meant only for use within the faceting module. Could be further generalized and made
* available for more use-cases outside faceting if there is a desire to do so.
*
* @lucene.experimental
*/
public abstract class MultiDoubleValuesSource implements SegmentCacheable {
/** Instantiates a new MultiDoubleValuesSource */
public MultiDoubleValuesSource() {}
/** Returns a {@link MultiDoubleValues} instance for the passed-in LeafReaderContext */
public abstract MultiDoubleValues getValues(LeafReaderContext ctx) throws IOException;
@Override
public abstract int hashCode();
@Override
public abstract boolean equals(Object o);
@Override
public abstract String toString();
/**
* Creates a MultiDoubleValuesSource that wraps a generic NumericDocValues/SortedNumericDocValues
* field. Uses the long-to-double decoding logic specified in {@code decoder} for converting the
* stored value to a double.
*/
public static MultiDoubleValuesSource fromField(String field, LongToDoubleFunction decoder) {
return new FieldMultiValuedSource(field, decoder);
}
/** Creates a MultiDoubleValuesSource that wraps a double-valued field */
public static MultiDoubleValuesSource fromDoubleField(String field) {
return fromField(field, Double::longBitsToDouble);
}
/** Creates a MultiDoubleValuesSource that wraps a float-valued field */
public static MultiDoubleValuesSource fromFloatField(String field) {
return fromField(field, v -> (double) Float.intBitsToFloat((int) v));
}
/** Creates a MultiDoubleValuesSource that wraps a long-valued field */
public static MultiDoubleValuesSource fromLongField(String field) {
return fromField(field, v -> (double) v);
}
/** Creates a MultiDoubleValuesSource that wraps an int-valued field */
public static MultiDoubleValuesSource fromIntField(String field) {
return fromLongField(field);
}
/** Creates a MultiDoubleValuesSource that wraps a single-valued {@code DoubleValuesSource} */
public static MultiDoubleValuesSource fromSingleValued(DoubleValuesSource singleValued) {
return new SingleValuedAsMultiValued(singleValued);
}
/**
* Returns a single-valued view of the {@code MultiDoubleValuesSource} if it was previously
* wrapped with {@link #fromSingleValued(DoubleValuesSource)}, or null.
*/
public static DoubleValuesSource unwrapSingleton(MultiDoubleValuesSource in) {
if (in instanceof SingleValuedAsMultiValued) {
return ((SingleValuedAsMultiValued) in).in;
} else {
return null;
}
}
/** Convert to a MultiLongValuesSource by casting the double values to longs */
public final MultiLongValuesSource toMultiLongValuesSource() {
return new LongDoubleValuesSource(this);
}
private static class FieldMultiValuedSource extends MultiDoubleValuesSource {
private final String field;
private final LongToDoubleFunction decoder;
FieldMultiValuedSource(String field, LongToDoubleFunction decoder) {
this.field = field;
this.decoder = decoder;
}
@Override
public MultiDoubleValues getValues(LeafReaderContext ctx) throws IOException {
final SortedNumericDocValues docValues = DocValues.getSortedNumeric(ctx.reader(), field);
return new MultiDoubleValues() {
@Override
public long getValueCount() {
return docValues.docValueCount();
}
@Override
public double nextValue() throws IOException {
return decoder.applyAsDouble(docValues.nextValue());
}
@Override
public boolean advanceExact(int doc) throws IOException {
return docValues.advanceExact(doc);
}
};
}
@Override
public boolean isCacheable(LeafReaderContext ctx) {
return DocValues.isCacheable(ctx, field);
}
@Override
public int hashCode() {
return Objects.hash(field, decoder);
}
@Override
public boolean equals(Object o) {
if (o == this) return true;
if (o == null || getClass() != o.getClass()) return false;
FieldMultiValuedSource that = (FieldMultiValuedSource) o;
return Objects.equals(field, that.field) && Objects.equals(decoder, that.decoder);
}
@Override
public String toString() {
return "multi-double(" + field + ")";
}
}
private static class SingleValuedAsMultiValued extends MultiDoubleValuesSource {
private final DoubleValuesSource in;
SingleValuedAsMultiValued(DoubleValuesSource in) {
this.in = in;
}
@Override
public MultiDoubleValues getValues(LeafReaderContext ctx) throws IOException {
final DoubleValues singleValues = in.getValues(ctx, null);
return new MultiDoubleValues() {
@Override
public long getValueCount() {
return 1;
}
@Override
public double nextValue() throws IOException {
return singleValues.doubleValue();
}
@Override
public boolean advanceExact(int doc) throws IOException {
return singleValues.advanceExact(doc);
}
};
}
@Override
public boolean isCacheable(LeafReaderContext ctx) {
return in.isCacheable(ctx);
}
@Override
public int hashCode() {
return Objects.hash(in);
}
@Override
public boolean equals(Object o) {
if (o == this) return true;
if (o == null || getClass() != o.getClass()) return false;
SingleValuedAsMultiValued that = (SingleValuedAsMultiValued) o;
return Objects.equals(in, that.in);
}
@Override
public String toString() {
return "multi-double(" + in + ")";
}
}
private static class LongDoubleValuesSource extends MultiLongValuesSource {
private final MultiDoubleValuesSource in;
LongDoubleValuesSource(MultiDoubleValuesSource in) {
this.in = in;
}
@Override
public MultiLongValues getValues(LeafReaderContext ctx) throws IOException {
final MultiDoubleValues doubleValues = in.getValues(ctx);
return new MultiLongValues() {
@Override
public long getValueCount() {
return doubleValues.getValueCount();
}
@Override
public long nextValue() throws IOException {
return (long) doubleValues.nextValue();
}
@Override
public boolean advanceExact(int doc) throws IOException {
return doubleValues.advanceExact(doc);
}
};
}
@Override
public boolean isCacheable(LeafReaderContext ctx) {
return in.isCacheable(ctx);
}
@Override
public int hashCode() {
return Objects.hash(in);
}
@Override
public boolean equals(Object o) {
if (o == this) return true;
if (o == null || getClass() != o.getClass()) return false;
LongDoubleValuesSource that = (LongDoubleValuesSource) o;
return Objects.equals(in, that.in);
}
@Override
public String toString() {
return "multi-double(" + in + ")";
}
}
}

View File

@ -0,0 +1,50 @@
/*
* 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.facet;
import java.io.IOException;
import org.apache.lucene.search.LongValues;
/**
* Per-segment, per-document long values, which can be calculated at search-time. Documents may
* produce multiple values. See also {@link LongValues} for a single-valued version.
*
* <p>Currently meant only for use within the faceting module. Could be further generalized and made
* available for more use-cases outside faceting if there is a desire to do so.
*
* @lucene.experimental
*/
public abstract class MultiLongValues {
/** Instantiates a new MultiLongValues */
public MultiLongValues() {}
/**
* Retrieves the number of values for the current document. This must always be greater than zero.
* It is illegal to call this method after {@link #advanceExact(int)} returned {@code false}.
*/
public abstract long getValueCount();
/**
* Iterates to the next value in the current document. Do not call this more than {@link
* #getValueCount} times for the document.
*/
public abstract long nextValue() throws IOException;
/** Advance to exactly {@code target} and return whether {@code target} has a value. */
public abstract boolean advanceExact(int doc) throws IOException;
}

View File

@ -0,0 +1,260 @@
/*
* 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.facet;
import java.io.IOException;
import java.util.Objects;
import org.apache.lucene.index.DocValues;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.index.SortedNumericDocValues;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.LongValues;
import org.apache.lucene.search.LongValuesSource;
import org.apache.lucene.search.SegmentCacheable;
/**
* Base class for producing {@link MultiLongValues}. See also {@link LongValuesSource} for a
* single-valued version.
*
* <p>MultiLongValuesSource objects for long and int-valued NumericDocValues/SortedNumericDocValues
* fields can be obtained by calling {@link #fromLongField(String)} and {@link
* #fromIntField(String)}. This is valid for both multi-valued and single-valued fields.
*
* <p>To obtain a MultiLongValuesSource from a float or double-valued
* NumericDocValues/SortedNumericDocValues field, use {@link
* MultiDoubleValuesSource#fromFloatField(String)} or {@link
* MultiDoubleValuesSource#fromDoubleField(String)} and then call {@link
* MultiDoubleValuesSource#toMultiLongValuesSource()}.
*
* <p>To obtain a MultiLongValuesSource from an existing {@link LongValuesSource}, see {@link
* #fromSingleValued(LongValuesSource)}. Instances created in this way can be "unwrapped" using
* {@link #unwrapSingleton(MultiLongValuesSource)} if necessary. Note that scores are never provided
* to the underlying {@code LongValuesSource}. {@link LongValuesSource#rewrite(IndexSearcher)} will
* also never be called. The user should be aware of this if using a {@code LongValuesSource} that
* relies on rewriting or scores. The faceting use-cases don't call rewrite or provide scores, which
* is why this simplification was made.
*
* <p>Currently meant only for use within the faceting module. Could be further generalized and made
* available for more use-cases outside faceting if there is a desire to do so.
*
* @lucene.experimental
*/
public abstract class MultiLongValuesSource implements SegmentCacheable {
/** Instantiates a new MultiLongValuesSource */
public MultiLongValuesSource() {}
/** Returns a {@link MultiLongValues} instance for the passed-in LeafReaderContext */
public abstract MultiLongValues getValues(LeafReaderContext ctx) throws IOException;
@Override
public abstract int hashCode();
@Override
public abstract boolean equals(Object o);
@Override
public abstract String toString();
/** Creates a MultiLongValuesSource that wraps a long-valued field */
public static MultiLongValuesSource fromLongField(String field) {
return new FieldMultiValueSource(field);
}
/** Creates a MultiLongValuesSource that wraps an int-valued field */
public static MultiLongValuesSource fromIntField(String field) {
return fromLongField(field);
}
/** Creates a MultiLongValuesSource that wraps a single-valued {@code LongValuesSource} */
public static MultiLongValuesSource fromSingleValued(LongValuesSource singleValued) {
return new SingleValuedAsMultiValued(singleValued);
}
/**
* Returns a single-valued view of the {@code MultiLongValuesSource} if it was previously wrapped
* with {@link #fromSingleValued(LongValuesSource)}, or null.
*/
public static LongValuesSource unwrapSingleton(MultiLongValuesSource in) {
if (in instanceof SingleValuedAsMultiValued) {
return ((SingleValuedAsMultiValued) in).in;
} else {
return null;
}
}
/** Convert to a MultiDoubleValuesSource by casting long values to doubles */
public final MultiDoubleValuesSource toMultiDoubleValuesSource() {
return new DoubleLongValuesSources(this);
}
private static class FieldMultiValueSource extends MultiLongValuesSource {
private final String field;
FieldMultiValueSource(String field) {
this.field = field;
}
@Override
public MultiLongValues getValues(LeafReaderContext ctx) throws IOException {
final SortedNumericDocValues docValues = DocValues.getSortedNumeric(ctx.reader(), field);
return new MultiLongValues() {
@Override
public long getValueCount() {
return docValues.docValueCount();
}
@Override
public long nextValue() throws IOException {
return docValues.nextValue();
}
@Override
public boolean advanceExact(int doc) throws IOException {
return docValues.advanceExact(doc);
}
};
}
@Override
public boolean isCacheable(LeafReaderContext ctx) {
return DocValues.isCacheable(ctx, field);
}
@Override
public int hashCode() {
return Objects.hash(field);
}
@Override
public boolean equals(Object o) {
if (o == this) return true;
if (o == null || getClass() != o.getClass()) return false;
FieldMultiValueSource that = (FieldMultiValueSource) o;
return Objects.equals(field, that.field);
}
@Override
public String toString() {
return "multi-long(" + field + ")";
}
}
private static class SingleValuedAsMultiValued extends MultiLongValuesSource {
private final LongValuesSource in;
SingleValuedAsMultiValued(LongValuesSource in) {
this.in = in;
}
@Override
public MultiLongValues getValues(LeafReaderContext ctx) throws IOException {
final LongValues singleValued = in.getValues(ctx, null);
return new MultiLongValues() {
@Override
public long getValueCount() {
return 1;
}
@Override
public long nextValue() throws IOException {
return singleValued.longValue();
}
@Override
public boolean advanceExact(int doc) throws IOException {
return singleValued.advanceExact(doc);
}
};
}
@Override
public boolean isCacheable(LeafReaderContext ctx) {
return in.isCacheable(ctx);
}
@Override
public int hashCode() {
return Objects.hash(in);
}
@Override
public boolean equals(Object o) {
if (o == this) return true;
if (o == null || getClass() != o.getClass()) return false;
SingleValuedAsMultiValued that = (SingleValuedAsMultiValued) o;
return Objects.equals(in, that.in);
}
@Override
public String toString() {
return "multi-long(" + in + ")";
}
}
private static class DoubleLongValuesSources extends MultiDoubleValuesSource {
private final MultiLongValuesSource in;
DoubleLongValuesSources(MultiLongValuesSource in) {
this.in = in;
}
@Override
public MultiDoubleValues getValues(LeafReaderContext ctx) throws IOException {
final MultiLongValues longValues = in.getValues(ctx);
return new MultiDoubleValues() {
@Override
public long getValueCount() {
return longValues.getValueCount();
}
@Override
public double nextValue() throws IOException {
return (double) longValues.nextValue();
}
@Override
public boolean advanceExact(int doc) throws IOException {
return longValues.advanceExact(doc);
}
};
}
@Override
public boolean isCacheable(LeafReaderContext ctx) {
return in.isCacheable(ctx);
}
@Override
public int hashCode() {
return Objects.hash(in);
}
@Override
public boolean equals(Object o) {
if (o == this) return true;
if (o == null || getClass() != o.getClass()) return false;
DoubleLongValuesSources that = (DoubleLongValuesSources) o;
return Objects.equals(in, that.in);
}
@Override
public String toString() {
return "multi-long(" + in + ")";
}
}
}

View File

@ -18,6 +18,8 @@ package org.apache.lucene.facet.range;
import java.io.IOException; import java.io.IOException;
import java.util.Objects; import java.util.Objects;
import org.apache.lucene.facet.MultiDoubleValues;
import org.apache.lucene.facet.MultiDoubleValuesSource;
import org.apache.lucene.index.IndexReader; import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.LeafReaderContext; import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.search.ConstantScoreScorer; import org.apache.lucene.search.ConstantScoreScorer;
@ -211,6 +213,112 @@ public final class DoubleRange extends Range {
} }
} }
private static class MultiValueSourceQuery extends Query {
private final DoubleRange range;
private final Query fastMatchQuery;
private final MultiDoubleValuesSource valueSource;
MultiValueSourceQuery(
DoubleRange range, Query fastMatchQuery, MultiDoubleValuesSource valueSource) {
this.range = range;
this.fastMatchQuery = fastMatchQuery;
this.valueSource = valueSource;
}
@Override
public boolean equals(Object other) {
return sameClassAs(other) && equalsTo(getClass().cast(other));
}
private boolean equalsTo(MultiValueSourceQuery other) {
return range.equals(other.range)
&& Objects.equals(fastMatchQuery, other.fastMatchQuery)
&& valueSource.equals(other.valueSource);
}
@Override
public int hashCode() {
return classHash() + 31 * Objects.hash(range, fastMatchQuery, valueSource);
}
@Override
public String toString(String field) {
return "Filter(" + range.toString() + ")";
}
@Override
public void visit(QueryVisitor visitor) {
visitor.visitLeaf(this);
}
@Override
public Query rewrite(IndexReader reader) throws IOException {
if (fastMatchQuery != null) {
final Query fastMatchRewritten = fastMatchQuery.rewrite(reader);
if (fastMatchRewritten != fastMatchQuery) {
return new MultiValueSourceQuery(range, fastMatchRewritten, valueSource);
}
}
return super.rewrite(reader);
}
@Override
public Weight createWeight(IndexSearcher searcher, ScoreMode scoreMode, float boost)
throws IOException {
final Weight fastMatchWeight =
fastMatchQuery == null
? null
: searcher.createWeight(fastMatchQuery, ScoreMode.COMPLETE_NO_SCORES, 1f);
return new ConstantScoreWeight(this, boost) {
@Override
public Scorer scorer(LeafReaderContext context) throws IOException {
final int maxDoc = context.reader().maxDoc();
final DocIdSetIterator approximation;
if (fastMatchWeight == null) {
approximation = DocIdSetIterator.all(maxDoc);
} else {
Scorer s = fastMatchWeight.scorer(context);
if (s == null) {
return null;
}
approximation = s.iterator();
}
final MultiDoubleValues values = valueSource.getValues(context);
final TwoPhaseIterator twoPhase =
new TwoPhaseIterator(approximation) {
@Override
public boolean matches() throws IOException {
if (values.advanceExact(approximation.docID()) == false) {
return false;
}
for (int i = 0; i < values.getValueCount(); i++) {
if (range.accept(values.nextValue())) {
return true;
}
}
return false;
}
@Override
public float matchCost() {
return 100; // TODO: use cost of range.accept()
}
};
return new ConstantScoreScorer(this, score(), scoreMode, twoPhase);
}
@Override
public boolean isCacheable(LeafReaderContext ctx) {
return valueSource.isCacheable(ctx);
}
};
}
}
/** /**
* Create a Query that matches documents in this range * Create a Query that matches documents in this range
* *
@ -226,4 +334,25 @@ public final class DoubleRange extends Range {
public Query getQuery(Query fastMatchQuery, DoubleValuesSource valueSource) { public Query getQuery(Query fastMatchQuery, DoubleValuesSource valueSource) {
return new ValueSourceQuery(this, fastMatchQuery, valueSource); return new ValueSourceQuery(this, fastMatchQuery, valueSource);
} }
/**
* Create a Query that matches documents in this range
*
* <p>The query will check all documents that match the provided match query, or every document in
* the index if the match query is null.
*
* <p>If the value source is static, eg an indexed numeric field, it may be faster to use {@link
* org.apache.lucene.search.PointRangeQuery}
*
* @param fastMatchQuery a query to use as a filter
* @param valueSource the source of values for the range check
*/
public Query getQuery(Query fastMatchQuery, MultiDoubleValuesSource valueSource) {
DoubleValuesSource singleValues = MultiDoubleValuesSource.unwrapSingleton(valueSource);
if (singleValues != null) {
return new ValueSourceQuery(this, fastMatchQuery, singleValues);
} else {
return new MultiValueSourceQuery(this, fastMatchQuery, valueSource);
}
}
} }

View File

@ -22,6 +22,8 @@ import org.apache.lucene.document.FloatDocValuesField;
import org.apache.lucene.facet.Facets; import org.apache.lucene.facet.Facets;
import org.apache.lucene.facet.FacetsCollector; import org.apache.lucene.facet.FacetsCollector;
import org.apache.lucene.facet.FacetsCollector.MatchingDocs; import org.apache.lucene.facet.FacetsCollector.MatchingDocs;
import org.apache.lucene.facet.MultiDoubleValues;
import org.apache.lucene.facet.MultiDoubleValuesSource;
import org.apache.lucene.index.NumericDocValues; import org.apache.lucene.index.NumericDocValues;
import org.apache.lucene.index.SortedNumericDocValues; import org.apache.lucene.index.SortedNumericDocValues;
import org.apache.lucene.search.DocIdSetIterator; import org.apache.lucene.search.DocIdSetIterator;
@ -50,13 +52,14 @@ public class DoubleRangeFacetCounts extends RangeFacetCounts {
* *
* <p>N.B This assumes that the field was indexed with {@link * <p>N.B This assumes that the field was indexed with {@link
* org.apache.lucene.document.DoubleDocValuesField}. For float-valued fields, use {@link * org.apache.lucene.document.DoubleDocValuesField}. For float-valued fields, use {@link
* #DoubleRangeFacetCounts(String, DoubleValuesSource, FacetsCollector, DoubleRange...)} * #DoubleRangeFacetCounts(String, DoubleValuesSource, FacetsCollector, DoubleRange...)} or {@link
* #DoubleRangeFacetCounts(String, MultiDoubleValuesSource, FacetsCollector, DoubleRange...)}
* *
* <p>TODO: Extend multi-valued support to fields that have been indexed as float values * <p>TODO: Extend multi-valued support to fields that have been indexed as float values
*/ */
public DoubleRangeFacetCounts(String field, FacetsCollector hits, DoubleRange... ranges) public DoubleRangeFacetCounts(String field, FacetsCollector hits, DoubleRange... ranges)
throws IOException { throws IOException {
this(field, null, hits, ranges); this(field, (DoubleValuesSource) null, hits, ranges);
} }
/** /**
@ -73,6 +76,24 @@ public class DoubleRangeFacetCounts extends RangeFacetCounts {
this(field, valueSource, hits, null, ranges); this(field, valueSource, hits, null, ranges);
} }
/**
* Create {@code RangeFacetCounts}, using the provided {@link MultiDoubleValuesSource} if
* non-null. If {@code valuesSource} is null, doc values from the provided {@code field} will be
* used.
*
* <p>N.B If relying on the provided {@code field}, see javadoc notes associated with {@link
* #DoubleRangeFacetCounts(String, FacetsCollector, DoubleRange...)} for assumptions on how the
* field is indexed.
*/
public DoubleRangeFacetCounts(
String field,
MultiDoubleValuesSource valuesSource,
FacetsCollector hits,
DoubleRange... ranges)
throws IOException {
this(field, valuesSource, hits, null, ranges);
}
/** /**
* Create {@code RangeFacetCounts}, using the provided {@link DoubleValuesSource} if non-null. If * Create {@code RangeFacetCounts}, using the provided {@link DoubleValuesSource} if non-null. If
* {@code valueSource} is null, doc values from the provided {@code field} will be used. Use the * {@code valueSource} is null, doc values from the provided {@code field} will be used. Use the
@ -100,6 +121,38 @@ public class DoubleRangeFacetCounts extends RangeFacetCounts {
} }
} }
/**
* Create {@code RangeFacetCounts}, using the provided {@link MultiDoubleValuesSource} if
* non-null. If {@code valuesSource} is null, doc values from the provided {@code field} will be
* used. Use the provided {@code Query} as a fastmatch: only documents matching the query are
* checked for the matching ranges.
*
* <p>N.B If relying on the provided {@code field}, see javadoc notes associated with {@link
* #DoubleRangeFacetCounts(String, FacetsCollector, DoubleRange...)} for assumptions on how the
* field is indexed.
*/
public DoubleRangeFacetCounts(
String field,
MultiDoubleValuesSource valuesSource,
FacetsCollector hits,
Query fastMatchQuery,
DoubleRange... ranges)
throws IOException {
super(field, ranges, fastMatchQuery);
// use the provided valueSource if non-null, otherwise use the doc values associated with the
// field
if (valuesSource != null) {
DoubleValuesSource singleValues = MultiDoubleValuesSource.unwrapSingleton(valuesSource);
if (singleValues != null) {
count(singleValues, hits.getMatchingDocs());
} else {
count(valuesSource, hits.getMatchingDocs());
}
} else {
count(field, hits.getMatchingDocs());
}
}
/** Counts from the provided valueSource. */ /** Counts from the provided valueSource. */
private void count(DoubleValuesSource valueSource, List<MatchingDocs> matchingDocs) private void count(DoubleValuesSource valueSource, List<MatchingDocs> matchingDocs)
throws IOException { throws IOException {
@ -134,6 +187,55 @@ public class DoubleRangeFacetCounts extends RangeFacetCounts {
totCount -= missingCount; totCount -= missingCount;
} }
/** Counts from the provided valueSource. */
private void count(MultiDoubleValuesSource valueSource, List<MatchingDocs> matchingDocs)
throws IOException {
LongRange[] longRanges = getLongRanges();
LongRangeCounter counter = LongRangeCounter.create(longRanges, counts);
int missingCount = 0;
for (MatchingDocs hits : matchingDocs) {
MultiDoubleValues multiValues = valueSource.getValues(hits.context);
final DocIdSetIterator it = createIterator(hits);
if (it == null) {
continue;
}
for (int doc = it.nextDoc(); doc != DocIdSetIterator.NO_MORE_DOCS; ) {
// Skip missing docs:
if (multiValues.advanceExact(doc)) {
long limit = multiValues.getValueCount();
// optimize single-valued case:
if (limit == 1) {
counter.addSingleValued(NumericUtils.doubleToSortableLong(multiValues.nextValue()));
totCount++;
} else {
counter.startMultiValuedDoc();
long previous = 0;
for (int i = 0; i < limit; i++) {
long val = NumericUtils.doubleToSortableLong(multiValues.nextValue());
if (i == 0 || val != previous) {
counter.addMultiValued(val);
previous = val;
}
}
if (counter.endMultiValuedDoc()) {
totCount++;
}
}
}
doc = it.nextDoc();
}
}
missingCount += counter.finish();
totCount -= missingCount;
}
/** Create long ranges from the double ranges. */ /** Create long ranges from the double ranges. */
@Override @Override
protected LongRange[] getLongRanges() { protected LongRange[] getLongRanges() {

View File

@ -18,6 +18,8 @@ package org.apache.lucene.facet.range;
import java.io.IOException; import java.io.IOException;
import java.util.Objects; import java.util.Objects;
import org.apache.lucene.facet.MultiLongValues;
import org.apache.lucene.facet.MultiLongValuesSource;
import org.apache.lucene.index.IndexReader; import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.LeafReaderContext; import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.search.ConstantScoreScorer; import org.apache.lucene.search.ConstantScoreScorer;
@ -198,6 +200,112 @@ public final class LongRange extends Range {
} }
} }
private static class MultiValueSourceQuery extends Query {
private final LongRange range;
private final Query fastMatchQuery;
private final MultiLongValuesSource valuesSource;
MultiValueSourceQuery(
LongRange range, Query fastMatchQuery, MultiLongValuesSource valuesSource) {
this.range = range;
this.fastMatchQuery = fastMatchQuery;
this.valuesSource = valuesSource;
}
@Override
public boolean equals(Object other) {
return sameClassAs(other) && equalsTo(getClass().cast(other));
}
private boolean equalsTo(MultiValueSourceQuery other) {
return range.equals(other.range)
&& Objects.equals(fastMatchQuery, other.fastMatchQuery)
&& valuesSource.equals(other.valuesSource);
}
@Override
public int hashCode() {
return classHash() + 31 * Objects.hash(range, fastMatchQuery, valuesSource);
}
@Override
public String toString(String field) {
return "Filter(" + range.toString() + ")";
}
@Override
public void visit(QueryVisitor visitor) {
visitor.visitLeaf(this);
}
@Override
public Query rewrite(IndexReader reader) throws IOException {
if (fastMatchQuery != null) {
final Query fastMatchRewritten = fastMatchQuery.rewrite(reader);
if (fastMatchRewritten != fastMatchQuery) {
return new MultiValueSourceQuery(range, fastMatchRewritten, valuesSource);
}
}
return super.rewrite(reader);
}
@Override
public Weight createWeight(IndexSearcher searcher, ScoreMode scoreMode, float boost)
throws IOException {
final Weight fastMatchWeight =
fastMatchQuery == null
? null
: searcher.createWeight(fastMatchQuery, ScoreMode.COMPLETE_NO_SCORES, 1f);
return new ConstantScoreWeight(this, boost) {
@Override
public Scorer scorer(LeafReaderContext context) throws IOException {
final int maxDoc = context.reader().maxDoc();
final DocIdSetIterator approximation;
if (fastMatchWeight == null) {
approximation = DocIdSetIterator.all(maxDoc);
} else {
Scorer s = fastMatchWeight.scorer(context);
if (s == null) {
return null;
}
approximation = s.iterator();
}
final MultiLongValues values = valuesSource.getValues(context);
final TwoPhaseIterator twoPhase =
new TwoPhaseIterator(approximation) {
@Override
public boolean matches() throws IOException {
if (values.advanceExact(approximation.docID()) == false) {
return false;
}
for (int i = 0; i < values.getValueCount(); i++) {
if (range.accept(values.nextValue())) {
return true;
}
}
return false;
}
@Override
public float matchCost() {
return 100; // TODO: use cost of range.accept()
}
};
return new ConstantScoreScorer(this, score(), scoreMode, twoPhase);
}
@Override
public boolean isCacheable(LeafReaderContext ctx) {
return valuesSource.isCacheable(ctx);
}
};
}
}
/** /**
* Create a Query that matches documents in this range * Create a Query that matches documents in this range
* *
@ -213,4 +321,25 @@ public final class LongRange extends Range {
public Query getQuery(Query fastMatchQuery, LongValuesSource valueSource) { public Query getQuery(Query fastMatchQuery, LongValuesSource valueSource) {
return new ValueSourceQuery(this, fastMatchQuery, valueSource); return new ValueSourceQuery(this, fastMatchQuery, valueSource);
} }
/**
* Create a Query that matches documents in this range
*
* <p>The query will check all documents that match the provided match query, or every document in
* the index if the match query is null.
*
* <p>If the value source is static, eg an indexed numeric field, it may be faster to use {@link
* org.apache.lucene.search.PointRangeQuery}
*
* @param fastMatchQuery a query to use as a filter
* @param valuesSource the source of values for the range check
*/
public Query getQuery(Query fastMatchQuery, MultiLongValuesSource valuesSource) {
LongValuesSource singleValues = MultiLongValuesSource.unwrapSingleton(valuesSource);
if (singleValues != null) {
return new ValueSourceQuery(this, fastMatchQuery, singleValues);
} else {
return new MultiValueSourceQuery(this, fastMatchQuery, valuesSource);
}
}
} }

View File

@ -21,6 +21,8 @@ import java.util.List;
import org.apache.lucene.facet.Facets; import org.apache.lucene.facet.Facets;
import org.apache.lucene.facet.FacetsCollector; import org.apache.lucene.facet.FacetsCollector;
import org.apache.lucene.facet.FacetsCollector.MatchingDocs; import org.apache.lucene.facet.FacetsCollector.MatchingDocs;
import org.apache.lucene.facet.MultiLongValues;
import org.apache.lucene.facet.MultiLongValuesSource;
import org.apache.lucene.index.NumericDocValues; import org.apache.lucene.index.NumericDocValues;
import org.apache.lucene.index.SortedNumericDocValues; import org.apache.lucene.index.SortedNumericDocValues;
import org.apache.lucene.search.DocIdSetIterator; import org.apache.lucene.search.DocIdSetIterator;
@ -45,7 +47,7 @@ public class LongRangeFacetCounts extends RangeFacetCounts {
*/ */
public LongRangeFacetCounts(String field, FacetsCollector hits, LongRange... ranges) public LongRangeFacetCounts(String field, FacetsCollector hits, LongRange... ranges)
throws IOException { throws IOException {
this(field, null, hits, ranges); this(field, (LongValuesSource) null, hits, ranges);
} }
/** /**
@ -58,6 +60,17 @@ public class LongRangeFacetCounts extends RangeFacetCounts {
this(field, valueSource, hits, null, ranges); this(field, valueSource, hits, null, ranges);
} }
/**
* Create {@code LongRangeFacetCounts}, using the provided {@link MultiLongValuesSource} if
* non-null. If {@code valuesSource} is null, doc values from the provided {@code field} will be
* used.
*/
public LongRangeFacetCounts(
String field, MultiLongValuesSource valuesSource, FacetsCollector hits, LongRange... ranges)
throws IOException {
this(field, valuesSource, hits, null, ranges);
}
/** /**
* Create {@code LongRangeFacetCounts}, using the provided {@link LongValuesSource} if non-null. * Create {@code LongRangeFacetCounts}, using the provided {@link LongValuesSource} if non-null.
* If {@code valueSource} is null, doc values from the provided {@code field} will be used. Use * If {@code valueSource} is null, doc values from the provided {@code field} will be used. Use
@ -83,12 +96,35 @@ public class LongRangeFacetCounts extends RangeFacetCounts {
} }
/** /**
* Counts from the provided valueSource. * Create {@code LongRangeFacetCounts}, using the provided {@link MultiLongValuesSource} if
* * non-null. If {@code valuesSource} is null, doc values from the provided {@code field} will be
* <p>TODO: Seems like we could extract this into RangeFacetCounts and make the logic common * used. Use the provided {@code Query} as a fastmatch: only documents passing the filter are
* between this class and DoubleRangeFacetCounts somehow. The blocker right now is that this * checked for the matching ranges, which is helpful when the provided {@link LongValuesSource} is
* implementation expects LongValueSource and DoubleRangeFacetCounts expects DoubleValueSource. * costly per-document, such as a geo distance.
*/ */
public LongRangeFacetCounts(
String field,
MultiLongValuesSource valuesSource,
FacetsCollector hits,
Query fastMatchQuery,
LongRange... ranges)
throws IOException {
super(field, ranges, fastMatchQuery);
// use the provided valueSource if non-null, otherwise use the doc values associated with the
// field
if (valuesSource != null) {
LongValuesSource singleValues = MultiLongValuesSource.unwrapSingleton(valuesSource);
if (singleValues != null) {
count(singleValues, hits.getMatchingDocs());
} else {
count(valuesSource, hits.getMatchingDocs());
}
} else {
count(field, hits.getMatchingDocs());
}
}
/** Counts from the provided valueSource. */
private void count(LongValuesSource valueSource, List<MatchingDocs> matchingDocs) private void count(LongValuesSource valueSource, List<MatchingDocs> matchingDocs)
throws IOException { throws IOException {
@ -123,6 +159,54 @@ public class LongRangeFacetCounts extends RangeFacetCounts {
totCount -= missingCount; totCount -= missingCount;
} }
/** Counts from the provided valueSource. */
private void count(MultiLongValuesSource valueSource, List<MatchingDocs> matchingDocs)
throws IOException {
LongRange[] ranges = getLongRanges();
LongRangeCounter counter = LongRangeCounter.create(ranges, counts);
for (MatchingDocs hits : matchingDocs) {
MultiLongValues multiValues = valueSource.getValues(hits.context);
final DocIdSetIterator it = createIterator(hits);
if (it == null) {
continue;
}
for (int doc = it.nextDoc(); doc != DocIdSetIterator.NO_MORE_DOCS; ) {
// Skip missing docs:
if (multiValues.advanceExact(doc)) {
long limit = multiValues.getValueCount();
// optimize single-valued case:
if (limit == 1) {
counter.addSingleValued(multiValues.nextValue());
totCount++;
} else {
counter.startMultiValuedDoc();
long previous = 0;
for (int i = 0; i < limit; i++) {
long val = multiValues.nextValue();
if (i == 0 || val != previous) {
counter.addMultiValued(val);
previous = val;
}
}
if (counter.endMultiValuedDoc()) {
totCount++;
}
}
}
doc = it.nextDoc();
}
}
int missingCount = counter.finish();
totCount -= missingCount;
}
@Override @Override
protected LongRange[] getLongRanges() { protected LongRange[] getLongRanges() {
return (LongRange[]) this.ranges; return (LongRange[]) this.ranges;

View File

@ -0,0 +1,199 @@
/*
* 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.facet;
import com.carrotsearch.randomizedtesting.generators.RandomNumbers;
import java.io.IOException;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.DoubleDocValuesField;
import org.apache.lucene.document.FloatDocValuesField;
import org.apache.lucene.document.NumericDocValuesField;
import org.apache.lucene.document.SortedNumericDocValuesField;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.NumericDocValues;
import org.apache.lucene.index.SortedNumericDocValues;
import org.apache.lucene.search.DoubleValues;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.LongValues;
import org.apache.lucene.store.Directory;
import org.apache.lucene.tests.index.RandomIndexWriter;
import org.apache.lucene.tests.util.LuceneTestCase;
public abstract class MultiValuesSourceTestCase extends LuceneTestCase {
protected Directory dir;
protected IndexReader reader;
protected IndexSearcher searcher;
@Override
public void setUp() throws Exception {
super.setUp();
dir = newDirectory();
RandomIndexWriter iw = new RandomIndexWriter(random(), dir);
int numDocs = RandomNumbers.randomIntBetween(random(), 100, 1000);
for (int i = 0; i < numDocs; i++) {
Document doc = new Document();
if (random().nextInt(10) < 8) {
doc.add(new NumericDocValuesField("single_int", random().nextInt()));
}
if (random().nextInt(10) < 8) {
doc.add(new NumericDocValuesField("single_long", random().nextLong()));
}
if (random().nextInt(10) < 8) {
doc.add(new FloatDocValuesField("single_float", random().nextFloat()));
}
if (random().nextInt(10) < 8) {
doc.add(new DoubleDocValuesField("single_double", random().nextDouble()));
}
int limit = RandomNumbers.randomIntBetween(random(), 0, 100);
for (int j = 0; j < limit; j++) {
doc.add(new SortedNumericDocValuesField("multi_int", random().nextInt()));
}
limit = RandomNumbers.randomIntBetween(random(), 0, 100);
for (int j = 0; j < limit; j++) {
doc.add(new SortedNumericDocValuesField("multi_long", random().nextLong()));
}
limit = RandomNumbers.randomIntBetween(random(), 0, 100);
for (int j = 0; j < limit; j++) {
doc.add(
new SortedNumericDocValuesField(
"multi_float", Float.floatToRawIntBits(random().nextFloat())));
}
limit = RandomNumbers.randomIntBetween(random(), 0, 100);
for (int j = 0; j < limit; j++) {
doc.add(
new SortedNumericDocValuesField(
"multi_double", Double.doubleToRawLongBits(random().nextDouble())));
}
iw.addDocument(doc);
if (i % 100 == 0 && random().nextBoolean()) {
iw.commit();
}
}
reader = iw.getReader();
iw.close();
searcher = newSearcher(reader);
}
@Override
public void tearDown() throws Exception {
reader.close();
dir.close();
super.tearDown();
}
protected void validateFieldBasedSource(NumericDocValues docValues, LongValues values, int maxDoc)
throws IOException {
for (int doc = 0; doc < maxDoc; doc++) {
boolean hasValues = docValues.advanceExact(doc);
assertEquals(hasValues, values.advanceExact(doc));
if (hasValues) {
assertEquals(docValues.longValue(), values.longValue());
}
}
}
protected void validateFieldBasedSource(
SortedNumericDocValues docValues, MultiLongValues values, int maxDoc) throws IOException {
for (int doc = 0; doc < maxDoc; doc++) {
boolean hasValues = docValues.advanceExact(doc);
assertEquals(hasValues, values.advanceExact(doc));
if (hasValues) {
int valueCount = docValues.docValueCount();
assertEquals(valueCount, values.getValueCount());
for (int i = 0; i < valueCount; i++) {
assertEquals(docValues.nextValue(), values.nextValue());
}
}
}
}
protected void validateFieldBasedSource(
NumericDocValues docValues, DoubleValues values, int maxDoc) throws IOException {
for (int doc = 0; doc < maxDoc; doc++) {
boolean hasValues = docValues.advanceExact(doc);
assertEquals(hasValues, values.advanceExact(doc));
if (hasValues) {
assertEquals((double) docValues.longValue(), values.doubleValue(), 0.00001);
}
}
}
protected void validateFieldBasedSource(
SortedNumericDocValues docValues, MultiDoubleValues values, int maxDoc) throws IOException {
for (int doc = 0; doc < maxDoc; doc++) {
boolean hasValues = docValues.advanceExact(doc);
assertEquals(hasValues, values.advanceExact(doc));
if (hasValues) {
int valueCount = docValues.docValueCount();
assertEquals(valueCount, values.getValueCount());
for (int i = 0; i < valueCount; i++) {
assertEquals((double) docValues.nextValue(), values.nextValue(), 0.00001);
}
}
}
}
protected void validateFieldBasedSource(
NumericDocValues docValues, DoubleValues values, int maxDoc, boolean useDoublePrecision)
throws IOException {
for (int doc = 0; doc < maxDoc; doc++) {
boolean hasValues = docValues.advanceExact(doc);
assertEquals(hasValues, values.advanceExact(doc));
if (hasValues) {
if (useDoublePrecision) {
long asLong = Double.doubleToRawLongBits(values.doubleValue());
assertEquals(docValues.longValue(), asLong);
} else {
int asInt = Float.floatToRawIntBits((float) values.doubleValue());
assertEquals((int) docValues.longValue(), asInt);
}
}
}
}
protected void validateFieldBasedSource(
SortedNumericDocValues docValues,
MultiDoubleValues values,
int maxDoc,
boolean useDoublePrecision)
throws IOException {
for (int doc = 0; doc < maxDoc; doc++) {
boolean hasValues = docValues.advanceExact(doc);
assertEquals(hasValues, values.advanceExact(doc));
if (hasValues) {
int valueCount = docValues.docValueCount();
assertEquals(valueCount, values.getValueCount());
for (int i = 0; i < valueCount; i++) {
if (useDoublePrecision) {
long asLong = Double.doubleToRawLongBits(values.nextValue());
assertEquals(docValues.nextValue(), asLong);
} else {
int asInt = Float.floatToRawIntBits((float) values.nextValue());
assertEquals(docValues.nextValue(), asInt);
}
}
}
}
}
}

View File

@ -236,8 +236,19 @@ public class TestLongValueFacetCounts extends LuceneTestCase {
if (VERBOSE) { if (VERBOSE) {
System.out.println(" use value source"); System.out.println(" use value source");
} }
if (random().nextBoolean()) {
facetCounts = facetCounts =
new LongValueFacetCounts("field", LongValuesSource.fromLongField("field"), fc); new LongValueFacetCounts("field", LongValuesSource.fromLongField("field"), fc);
} else if (random().nextBoolean()) {
facetCounts =
new LongValueFacetCounts("field", MultiLongValuesSource.fromLongField("field"), fc);
} else {
facetCounts =
new LongValueFacetCounts(
"field",
MultiLongValuesSource.fromSingleValued(LongValuesSource.fromLongField("field")),
fc);
}
} else { } else {
if (VERBOSE) { if (VERBOSE) {
System.out.println(" use doc values"); System.out.println(" use doc values");
@ -250,8 +261,19 @@ public class TestLongValueFacetCounts extends LuceneTestCase {
if (VERBOSE) { if (VERBOSE) {
System.out.println(" count all value source"); System.out.println(" count all value source");
} }
if (random().nextBoolean()) {
facetCounts = facetCounts =
new LongValueFacetCounts("field", LongValuesSource.fromLongField("field"), r); new LongValueFacetCounts("field", LongValuesSource.fromLongField("field"), r);
} else if (random().nextBoolean()) {
facetCounts =
new LongValueFacetCounts("field", MultiLongValuesSource.fromLongField("field"), r);
} else {
facetCounts =
new LongValueFacetCounts(
"field",
MultiLongValuesSource.fromSingleValued(LongValuesSource.fromLongField("field")),
r);
}
} else { } else {
if (VERBOSE) { if (VERBOSE) {
System.out.println(" count all doc values"); System.out.println(" count all doc values");
@ -320,8 +342,19 @@ public class TestLongValueFacetCounts extends LuceneTestCase {
if (VERBOSE) { if (VERBOSE) {
System.out.println(" use value source"); System.out.println(" use value source");
} }
if (random().nextBoolean()) {
facetCounts = facetCounts =
new LongValueFacetCounts("field", LongValuesSource.fromLongField("field"), fc); new LongValueFacetCounts("field", LongValuesSource.fromLongField("field"), fc);
} else if (random().nextBoolean()) {
facetCounts =
new LongValueFacetCounts("field", MultiLongValuesSource.fromLongField("field"), fc);
} else {
facetCounts =
new LongValueFacetCounts(
"field",
MultiLongValuesSource.fromSingleValued(LongValuesSource.fromLongField("field")),
fc);
}
} }
expected = new HashMap<>(); expected = new HashMap<>();
@ -476,13 +509,23 @@ public class TestLongValueFacetCounts extends LuceneTestCase {
if (VERBOSE) { if (VERBOSE) {
System.out.println(" use doc values"); System.out.println(" use doc values");
} }
if (random().nextBoolean()) {
facetCounts = new LongValueFacetCounts("field", fc); facetCounts = new LongValueFacetCounts("field", fc);
} else {
facetCounts =
new LongValueFacetCounts("field", MultiLongValuesSource.fromLongField("field"), fc);
}
} else { } else {
// optimized count all: // optimized count all:
if (VERBOSE) { if (VERBOSE) {
System.out.println(" count all doc values"); System.out.println(" count all doc values");
} }
if (random().nextBoolean()) {
facetCounts = new LongValueFacetCounts("field", r); facetCounts = new LongValueFacetCounts("field", r);
} else {
facetCounts =
new LongValueFacetCounts("field", MultiLongValuesSource.fromLongField("field"), r);
}
} }
FacetResult actual = facetCounts.getAllChildrenSortByValue(); FacetResult actual = facetCounts.getAllChildrenSortByValue();
@ -536,8 +579,12 @@ public class TestLongValueFacetCounts extends LuceneTestCase {
fc = new FacetsCollector(); fc = new FacetsCollector();
s.search(IntPoint.newRangeQuery("id", minId, maxId), fc); s.search(IntPoint.newRangeQuery("id", minId, maxId), fc);
// cannot use value source here because we are multi valued if (random().nextBoolean()) {
facetCounts = new LongValueFacetCounts("field", fc); facetCounts = new LongValueFacetCounts("field", fc);
} else {
facetCounts =
new LongValueFacetCounts("field", MultiLongValuesSource.fromLongField("field"), fc);
}
expected = new HashMap<>(); expected = new HashMap<>();
expectedTotalCount = 0; expectedTotalCount = 0;

View File

@ -0,0 +1,169 @@
/*
* 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.facet;
import org.apache.lucene.index.DocValues;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.index.NumericDocValues;
import org.apache.lucene.index.SortedNumericDocValues;
import org.apache.lucene.search.DoubleValues;
import org.apache.lucene.search.DoubleValuesSource;
public class TestMultiDoubleValuesSource extends MultiValuesSourceTestCase {
public void testRandom() throws Exception {
MultiDoubleValuesSource valuesSource;
valuesSource = MultiDoubleValuesSource.fromIntField("single_int");
assertNotNull(valuesSource);
for (LeafReaderContext ctx : reader.leaves()) {
SortedNumericDocValues docValues = DocValues.getSortedNumeric(ctx.reader(), "single_int");
MultiDoubleValues values = valuesSource.getValues(ctx);
validateFieldBasedSource(docValues, values, ctx.reader().maxDoc());
}
valuesSource = MultiDoubleValuesSource.fromLongField("single_long");
assertNotNull(valuesSource);
for (LeafReaderContext ctx : reader.leaves()) {
SortedNumericDocValues docValues = DocValues.getSortedNumeric(ctx.reader(), "single_long");
MultiDoubleValues values = valuesSource.getValues(ctx);
validateFieldBasedSource(docValues, values, ctx.reader().maxDoc());
}
valuesSource = MultiDoubleValuesSource.fromIntField("multi_int");
for (LeafReaderContext ctx : reader.leaves()) {
SortedNumericDocValues docValues = DocValues.getSortedNumeric(ctx.reader(), "multi_int");
MultiDoubleValues values = valuesSource.getValues(ctx);
validateFieldBasedSource(docValues, values, ctx.reader().maxDoc());
}
valuesSource = MultiDoubleValuesSource.fromLongField("multi_long");
for (LeafReaderContext ctx : reader.leaves()) {
SortedNumericDocValues docValues = DocValues.getSortedNumeric(ctx.reader(), "multi_long");
MultiDoubleValues values = valuesSource.getValues(ctx);
validateFieldBasedSource(docValues, values, ctx.reader().maxDoc());
}
valuesSource = MultiDoubleValuesSource.fromFloatField("single_float");
assertNotNull(valuesSource);
for (LeafReaderContext ctx : reader.leaves()) {
SortedNumericDocValues docValues = DocValues.getSortedNumeric(ctx.reader(), "single_float");
MultiDoubleValues values = valuesSource.getValues(ctx);
validateFieldBasedSource(docValues, values, ctx.reader().maxDoc(), false);
}
valuesSource = MultiDoubleValuesSource.fromDoubleField("single_double");
assertNotNull(valuesSource);
for (LeafReaderContext ctx : reader.leaves()) {
SortedNumericDocValues docValues = DocValues.getSortedNumeric(ctx.reader(), "single_double");
MultiDoubleValues values = valuesSource.getValues(ctx);
validateFieldBasedSource(docValues, values, ctx.reader().maxDoc(), true);
}
valuesSource = MultiDoubleValuesSource.fromFloatField("multi_float");
for (LeafReaderContext ctx : reader.leaves()) {
SortedNumericDocValues docValues = DocValues.getSortedNumeric(ctx.reader(), "multi_float");
MultiDoubleValues values = valuesSource.getValues(ctx);
validateFieldBasedSource(docValues, values, ctx.reader().maxDoc(), false);
}
valuesSource = MultiDoubleValuesSource.fromDoubleField("multi_double");
for (LeafReaderContext ctx : reader.leaves()) {
SortedNumericDocValues docValues = DocValues.getSortedNumeric(ctx.reader(), "multi_double");
MultiDoubleValues values = valuesSource.getValues(ctx);
validateFieldBasedSource(docValues, values, ctx.reader().maxDoc(), true);
}
}
public void testFromSingleValued() throws Exception {
MultiDoubleValuesSource valuesSource;
DoubleValuesSource singleton;
valuesSource =
MultiDoubleValuesSource.fromSingleValued(DoubleValuesSource.fromFloatField("single_float"));
singleton = MultiDoubleValuesSource.unwrapSingleton(valuesSource);
assertNotNull(singleton);
for (LeafReaderContext ctx : reader.leaves()) {
SortedNumericDocValues docValues = DocValues.getSortedNumeric(ctx.reader(), "single_float");
MultiDoubleValues values = valuesSource.getValues(ctx);
validateFieldBasedSource(docValues, values, ctx.reader().maxDoc(), false);
NumericDocValues singletonDv = DocValues.getNumeric(ctx.reader(), "single_float");
DoubleValues singletonVals = singleton.getValues(ctx, null);
validateFieldBasedSource(singletonDv, singletonVals, ctx.reader().maxDoc(), false);
}
valuesSource =
MultiDoubleValuesSource.fromSingleValued(
DoubleValuesSource.fromDoubleField("single_double"));
singleton = MultiDoubleValuesSource.unwrapSingleton(valuesSource);
assertNotNull(singleton);
for (LeafReaderContext ctx : reader.leaves()) {
SortedNumericDocValues docValues = DocValues.getSortedNumeric(ctx.reader(), "single_double");
MultiDoubleValues values = valuesSource.getValues(ctx);
validateFieldBasedSource(docValues, values, ctx.reader().maxDoc(), true);
NumericDocValues singletonDv = DocValues.getNumeric(ctx.reader(), "single_double");
DoubleValues singletonVals = singleton.getValues(ctx, null);
validateFieldBasedSource(singletonDv, singletonVals, ctx.reader().maxDoc(), true);
}
}
public void testCacheable() {
MultiDoubleValuesSource valuesSource = MultiDoubleValuesSource.fromDoubleField("multi_double");
for (LeafReaderContext ctx : searcher.getIndexReader().leaves()) {
assertEquals(DocValues.isCacheable(ctx, "multi_double"), valuesSource.isCacheable(ctx));
}
}
public void testEqualsAndHashcode() {
MultiDoubleValuesSource valuesSource1 = MultiDoubleValuesSource.fromLongField("multi_long");
MultiDoubleValuesSource valuesSource2 = MultiDoubleValuesSource.fromLongField("multi_long");
MultiDoubleValuesSource valuesSource3 = MultiDoubleValuesSource.fromLongField("multi_int");
assertEquals(valuesSource1, valuesSource2);
assertNotEquals(valuesSource1, valuesSource3);
assertEquals(valuesSource1.hashCode(), valuesSource2.hashCode());
assertNotEquals(valuesSource1.hashCode(), valuesSource3.hashCode());
valuesSource1 =
MultiDoubleValuesSource.fromSingleValued(DoubleValuesSource.fromLongField("single_long"));
valuesSource2 =
MultiDoubleValuesSource.fromSingleValued(DoubleValuesSource.fromLongField("single_long"));
valuesSource3 =
MultiDoubleValuesSource.fromSingleValued(DoubleValuesSource.fromLongField("single_int"));
assertEquals(valuesSource1, valuesSource2);
assertNotEquals(valuesSource1, valuesSource3);
assertEquals(valuesSource1.hashCode(), valuesSource2.hashCode());
assertNotEquals(valuesSource1.hashCode(), valuesSource3.hashCode());
DoubleValuesSource singleton1 = MultiDoubleValuesSource.unwrapSingleton(valuesSource1);
DoubleValuesSource singleton2 = MultiDoubleValuesSource.unwrapSingleton(valuesSource2);
DoubleValuesSource singleton3 = MultiDoubleValuesSource.unwrapSingleton(valuesSource3);
assertEquals(singleton1, singleton2);
assertNotEquals(singleton1, singleton3);
assertEquals(singleton1.hashCode(), singleton2.hashCode());
assertNotEquals(singleton1.hashCode(), singleton3.hashCode());
valuesSource1 = MultiDoubleValuesSource.fromField("single_long", Long::valueOf);
valuesSource2 = MultiDoubleValuesSource.fromField("single_long", v -> -1 * v);
valuesSource3 = MultiDoubleValuesSource.fromField("single_int", Long::valueOf);
assertNotEquals(valuesSource1, valuesSource2);
assertNotEquals(valuesSource1, valuesSource3);
assertNotEquals(valuesSource1.hashCode(), valuesSource2.hashCode());
assertNotEquals(valuesSource1.hashCode(), valuesSource3.hashCode());
}
}

View File

@ -0,0 +1,148 @@
/*
* 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.facet;
import org.apache.lucene.index.DocValues;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.index.NumericDocValues;
import org.apache.lucene.index.SortedNumericDocValues;
import org.apache.lucene.search.LongValues;
import org.apache.lucene.search.LongValuesSource;
public class TestMultiLongValuesSource extends MultiValuesSourceTestCase {
public void testRandom() throws Exception {
MultiLongValuesSource valuesSource;
valuesSource = MultiLongValuesSource.fromIntField("single_int");
assertNotNull(valuesSource);
for (LeafReaderContext ctx : reader.leaves()) {
SortedNumericDocValues docValues = DocValues.getSortedNumeric(ctx.reader(), "single_int");
MultiLongValues values = valuesSource.getValues(ctx);
validateFieldBasedSource(docValues, values, ctx.reader().maxDoc());
}
valuesSource = MultiLongValuesSource.fromLongField("single_long");
assertNotNull(valuesSource);
for (LeafReaderContext ctx : reader.leaves()) {
SortedNumericDocValues docValues = DocValues.getSortedNumeric(ctx.reader(), "single_long");
MultiLongValues values = valuesSource.getValues(ctx);
validateFieldBasedSource(docValues, values, ctx.reader().maxDoc());
}
valuesSource = MultiLongValuesSource.fromIntField("multi_int");
for (LeafReaderContext ctx : reader.leaves()) {
SortedNumericDocValues docValues = DocValues.getSortedNumeric(ctx.reader(), "multi_int");
MultiLongValues values = valuesSource.getValues(ctx);
validateFieldBasedSource(docValues, values, ctx.reader().maxDoc());
}
valuesSource = MultiLongValuesSource.fromLongField("multi_long");
for (LeafReaderContext ctx : reader.leaves()) {
SortedNumericDocValues docValues = DocValues.getSortedNumeric(ctx.reader(), "multi_long");
MultiLongValues values = valuesSource.getValues(ctx);
validateFieldBasedSource(docValues, values, ctx.reader().maxDoc());
}
}
public void testFromSingleValued() throws Exception {
MultiLongValuesSource valuesSource;
LongValuesSource singleton;
valuesSource =
MultiLongValuesSource.fromSingleValued(LongValuesSource.fromIntField("single_int"));
singleton = MultiLongValuesSource.unwrapSingleton(valuesSource);
assertNotNull(singleton);
for (LeafReaderContext ctx : reader.leaves()) {
SortedNumericDocValues docValues = DocValues.getSortedNumeric(ctx.reader(), "single_int");
MultiLongValues values = valuesSource.getValues(ctx);
validateFieldBasedSource(docValues, values, ctx.reader().maxDoc());
NumericDocValues singletonDv = DocValues.getNumeric(ctx.reader(), "single_int");
LongValues singletonVals = singleton.getValues(ctx, null);
validateFieldBasedSource(singletonDv, singletonVals, ctx.reader().maxDoc());
}
valuesSource =
MultiLongValuesSource.fromSingleValued(LongValuesSource.fromLongField("single_long"));
singleton = MultiLongValuesSource.unwrapSingleton(valuesSource);
assertNotNull(singleton);
for (LeafReaderContext ctx : reader.leaves()) {
SortedNumericDocValues docValues = DocValues.getSortedNumeric(ctx.reader(), "single_long");
MultiLongValues values = valuesSource.getValues(ctx);
validateFieldBasedSource(docValues, values, ctx.reader().maxDoc());
NumericDocValues singletonDv = DocValues.getNumeric(ctx.reader(), "single_long");
LongValues singletonVals = singleton.getValues(ctx, null);
validateFieldBasedSource(singletonDv, singletonVals, ctx.reader().maxDoc());
}
}
public void testToDouble() throws Exception {
MultiDoubleValuesSource valuesSource =
MultiLongValuesSource.fromLongField("multi_long").toMultiDoubleValuesSource();
for (LeafReaderContext ctx : reader.leaves()) {
SortedNumericDocValues docValues = DocValues.getSortedNumeric(ctx.reader(), "multi_long");
MultiDoubleValues values = valuesSource.getValues(ctx);
validateFieldBasedSource(docValues, values, ctx.reader().maxDoc());
}
}
public void testCacheable() {
MultiLongValuesSource valuesSource = MultiLongValuesSource.fromLongField("multi_long");
for (LeafReaderContext ctx : searcher.getIndexReader().leaves()) {
assertEquals(DocValues.isCacheable(ctx, "multi_long"), valuesSource.isCacheable(ctx));
}
}
public void testEqualsAndHashcode() {
MultiLongValuesSource valuesSource1 = MultiLongValuesSource.fromLongField("multi_long");
MultiLongValuesSource valuesSource2 = MultiLongValuesSource.fromLongField("multi_long");
MultiLongValuesSource valuesSource3 = MultiLongValuesSource.fromLongField("multi_int");
assertEquals(valuesSource1, valuesSource2);
assertNotEquals(valuesSource1, valuesSource3);
assertEquals(valuesSource1.hashCode(), valuesSource2.hashCode());
assertNotEquals(valuesSource1.hashCode(), valuesSource3.hashCode());
valuesSource1 =
MultiLongValuesSource.fromSingleValued(LongValuesSource.fromLongField("single_long"));
valuesSource2 =
MultiLongValuesSource.fromSingleValued(LongValuesSource.fromLongField("single_long"));
valuesSource3 =
MultiLongValuesSource.fromSingleValued(LongValuesSource.fromLongField("single_int"));
assertEquals(valuesSource1, valuesSource2);
assertNotEquals(valuesSource1, valuesSource3);
assertEquals(valuesSource1.hashCode(), valuesSource2.hashCode());
assertNotEquals(valuesSource1.hashCode(), valuesSource3.hashCode());
LongValuesSource singleton1 = MultiLongValuesSource.unwrapSingleton(valuesSource1);
LongValuesSource singleton2 = MultiLongValuesSource.unwrapSingleton(valuesSource2);
LongValuesSource singleton3 = MultiLongValuesSource.unwrapSingleton(valuesSource3);
assertEquals(singleton1, singleton2);
assertNotEquals(singleton1, singleton3);
assertEquals(singleton1.hashCode(), singleton2.hashCode());
assertNotEquals(singleton1.hashCode(), singleton3.hashCode());
MultiDoubleValuesSource doubleValuesSource1 = valuesSource1.toMultiDoubleValuesSource();
MultiDoubleValuesSource doubleValuesSource2 = valuesSource2.toMultiDoubleValuesSource();
MultiDoubleValuesSource doubleValuesSource3 = valuesSource3.toMultiDoubleValuesSource();
assertEquals(doubleValuesSource1, doubleValuesSource2);
assertNotEquals(doubleValuesSource1, doubleValuesSource3);
assertEquals(doubleValuesSource1.hashCode(), doubleValuesSource2.hashCode());
assertNotEquals(doubleValuesSource1.hashCode(), doubleValuesSource3.hashCode());
}
}

View File

@ -38,7 +38,9 @@ import org.apache.lucene.facet.Facets;
import org.apache.lucene.facet.FacetsCollector; import org.apache.lucene.facet.FacetsCollector;
import org.apache.lucene.facet.FacetsConfig; import org.apache.lucene.facet.FacetsConfig;
import org.apache.lucene.facet.LabelAndValue; import org.apache.lucene.facet.LabelAndValue;
import org.apache.lucene.facet.MultiDoubleValuesSource;
import org.apache.lucene.facet.MultiFacets; import org.apache.lucene.facet.MultiFacets;
import org.apache.lucene.facet.MultiLongValuesSource;
import org.apache.lucene.facet.taxonomy.TaxonomyReader; import org.apache.lucene.facet.taxonomy.TaxonomyReader;
import org.apache.lucene.facet.taxonomy.directory.DirectoryTaxonomyReader; import org.apache.lucene.facet.taxonomy.directory.DirectoryTaxonomyReader;
import org.apache.lucene.facet.taxonomy.directory.DirectoryTaxonomyWriter; import org.apache.lucene.facet.taxonomy.directory.DirectoryTaxonomyWriter;
@ -766,7 +768,45 @@ public class TestRangeFacetCounts extends FacetTestCase {
} else { } else {
fastMatchQuery = null; fastMatchQuery = null;
} }
if (random().nextBoolean()) {
LongValuesSource vs = LongValuesSource.fromLongField("field"); LongValuesSource vs = LongValuesSource.fromLongField("field");
MultiLongValuesSource mvs = MultiLongValuesSource.fromLongField("field");
Facets facets;
if (random().nextBoolean()) {
facets = new LongRangeFacetCounts("field", vs, sfc, fastMatchQuery, ranges);
} else if (random().nextBoolean()) {
facets = new LongRangeFacetCounts("field", mvs, sfc, fastMatchQuery, ranges);
} else {
facets =
new LongRangeFacetCounts(
"field", MultiLongValuesSource.fromSingleValued(vs), sfc, fastMatchQuery, ranges);
}
FacetResult result = facets.getTopChildren(10, "field");
assertEquals(numRange, result.labelValues.length);
for (int rangeID = 0; rangeID < numRange; rangeID++) {
if (VERBOSE) {
System.out.println(" range " + rangeID + " expectedCount=" + expectedCounts[rangeID]);
}
LabelAndValue subNode = result.labelValues[rangeID];
assertEquals("r" + rangeID, subNode.label);
assertEquals(expectedCounts[rangeID], subNode.value.intValue());
LongRange range = ranges[rangeID];
// Test drill-down:
DrillDownQuery ddq = new DrillDownQuery(config);
if (random().nextBoolean()) {
ddq.add("field", LongPoint.newRangeQuery("field", range.min, range.max));
} else if (random().nextBoolean()) {
ddq.add("field", range.getQuery(fastMatchQuery, mvs));
} else {
ddq.add("field", range.getQuery(fastMatchQuery, vs));
}
assertEquals(expectedCounts[rangeID], s.count(ddq));
}
} else {
MultiLongValuesSource vs = MultiLongValuesSource.fromLongField("field");
Facets facets = new LongRangeFacetCounts("field", vs, sfc, fastMatchQuery, ranges); Facets facets = new LongRangeFacetCounts("field", vs, sfc, fastMatchQuery, ranges);
FacetResult result = facets.getTopChildren(10, "field"); FacetResult result = facets.getTopChildren(10, "field");
assertEquals(numRange, result.labelValues.length); assertEquals(numRange, result.labelValues.length);
@ -790,6 +830,7 @@ public class TestRangeFacetCounts extends FacetTestCase {
assertEquals(expectedCounts[rangeID], s.count(ddq)); assertEquals(expectedCounts[rangeID], s.count(ddq));
} }
} }
}
w.close(); w.close();
IOUtils.close(r, dir); IOUtils.close(r, dir);
@ -924,7 +965,16 @@ public class TestRangeFacetCounts extends FacetTestCase {
} else { } else {
fastMatchQuery = null; fastMatchQuery = null;
} }
Facets facets = new LongRangeFacetCounts("field", null, sfc, fastMatchQuery, ranges); Facets facets;
if (random().nextBoolean()) {
facets =
new LongRangeFacetCounts(
"field", MultiLongValuesSource.fromLongField("field"), sfc, fastMatchQuery, ranges);
} else {
facets =
new LongRangeFacetCounts(
"field", (MultiLongValuesSource) null, sfc, fastMatchQuery, ranges);
}
FacetResult result = facets.getTopChildren(10, "field"); FacetResult result = facets.getTopChildren(10, "field");
assertEquals(numRange, result.labelValues.length); assertEquals(numRange, result.labelValues.length);
for (int rangeID = 0; rangeID < numRange; rangeID++) { for (int rangeID = 0; rangeID < numRange; rangeID++) {
@ -1069,7 +1119,21 @@ public class TestRangeFacetCounts extends FacetTestCase {
fastMatchFilter = null; fastMatchFilter = null;
} }
DoubleValuesSource vs = DoubleValuesSource.fromDoubleField("field"); DoubleValuesSource vs = DoubleValuesSource.fromDoubleField("field");
Facets facets = new DoubleRangeFacetCounts("field", vs, sfc, fastMatchFilter, ranges); MultiDoubleValuesSource mvs = MultiDoubleValuesSource.fromDoubleField("field");
Facets facets;
if (random().nextBoolean()) {
facets = new DoubleRangeFacetCounts("field", vs, sfc, fastMatchFilter, ranges);
} else if (random().nextBoolean()) {
facets =
new DoubleRangeFacetCounts(
"field",
MultiDoubleValuesSource.fromSingleValued(vs),
sfc,
fastMatchFilter,
ranges);
} else {
facets = new DoubleRangeFacetCounts("field", mvs, sfc, fastMatchFilter, ranges);
}
FacetResult result = facets.getTopChildren(10, "field"); FacetResult result = facets.getTopChildren(10, "field");
assertEquals(numRange, result.labelValues.length); assertEquals(numRange, result.labelValues.length);
for (int rangeID = 0; rangeID < numRange; rangeID++) { for (int rangeID = 0; rangeID < numRange; rangeID++) {
@ -1086,8 +1150,10 @@ public class TestRangeFacetCounts extends FacetTestCase {
DrillDownQuery ddq = new DrillDownQuery(config); DrillDownQuery ddq = new DrillDownQuery(config);
if (random().nextBoolean()) { if (random().nextBoolean()) {
ddq.add("field", DoublePoint.newRangeQuery("field", range.min, range.max)); ddq.add("field", DoublePoint.newRangeQuery("field", range.min, range.max));
} else { } else if (random().nextBoolean()) {
ddq.add("field", range.getQuery(fastMatchFilter, vs)); ddq.add("field", range.getQuery(fastMatchFilter, vs));
} else {
ddq.add("field", range.getQuery(fastMatchFilter, mvs));
} }
assertEquals(expectedCounts[rangeID], s.count(ddq)); assertEquals(expectedCounts[rangeID], s.count(ddq));
@ -1222,7 +1288,20 @@ public class TestRangeFacetCounts extends FacetTestCase {
} else { } else {
fastMatchFilter = null; fastMatchFilter = null;
} }
Facets facets = new DoubleRangeFacetCounts("field", null, sfc, fastMatchFilter, ranges); Facets facets;
if (random().nextBoolean()) {
facets =
new DoubleRangeFacetCounts(
"field",
MultiDoubleValuesSource.fromDoubleField("field"),
sfc,
fastMatchFilter,
ranges);
} else {
facets =
new DoubleRangeFacetCounts(
"field", (MultiDoubleValuesSource) null, sfc, fastMatchFilter, ranges);
}
FacetResult result = facets.getTopChildren(10, "field"); FacetResult result = facets.getTopChildren(10, "field");
assertEquals(numRange, result.labelValues.length); assertEquals(numRange, result.labelValues.length);
for (int rangeID = 0; rangeID < numRange; rangeID++) { for (int rangeID = 0; rangeID < numRange; rangeID++) {
@ -1496,7 +1575,14 @@ public class TestRangeFacetCounts extends FacetTestCase {
System.out.println("TEST: fastMatchFilter=" + fastMatchFilter); System.out.println("TEST: fastMatchFilter=" + fastMatchFilter);
} }
Facets facets = new DoubleRangeFacetCounts("field", vs, fc, fastMatchFilter, ranges); Facets facets;
if (random().nextBoolean()) {
facets = new DoubleRangeFacetCounts("field", vs, fc, fastMatchFilter, ranges);
} else {
facets =
new DoubleRangeFacetCounts(
"field", MultiDoubleValuesSource.fromSingleValued(vs), fc, fastMatchFilter, ranges);
}
assertEquals( assertEquals(
"dim=field path=[] value=3 childCount=6\n < 1 (0)\n < 2 (1)\n < 5 (3)\n < 10 (3)\n < 20 (3)\n < 50 (3)\n", "dim=field path=[] value=3 childCount=6\n < 1 (0)\n < 2 (1)\n < 5 (3)\n < 10 (3)\n < 20 (3)\n < 50 (3)\n",
@ -1504,7 +1590,13 @@ public class TestRangeFacetCounts extends FacetTestCase {
assertTrue(fastMatchFilter == null || filterWasUsed.get()); assertTrue(fastMatchFilter == null || filterWasUsed.get());
DrillDownQuery ddq = new DrillDownQuery(config); DrillDownQuery ddq = new DrillDownQuery(config);
if (random().nextBoolean()) {
ddq.add("field", ranges[1].getQuery(fastMatchFilter, vs)); ddq.add("field", ranges[1].getQuery(fastMatchFilter, vs));
} else {
ddq.add(
"field",
ranges[1].getQuery(fastMatchFilter, MultiDoubleValuesSource.fromSingleValued(vs)));
}
// Test simple drill-down: // Test simple drill-down:
assertEquals(1, s.search(ddq, 10).totalHits.value); assertEquals(1, s.search(ddq, 10).totalHits.value);
@ -1521,7 +1613,11 @@ public class TestRangeFacetCounts extends FacetTestCase {
throws IOException { throws IOException {
assert drillSideways.length == 1; assert drillSideways.length == 1;
return new DoubleRangeFacetCounts( return new DoubleRangeFacetCounts(
"field", vs, drillSideways[0], fastMatchFilter, ranges); "field",
MultiDoubleValuesSource.fromSingleValued(vs),
drillSideways[0],
fastMatchFilter,
ranges);
} }
@Override @Override