blob: 81fdf6207d2ff1899a353b4a6f876234dd0be9d1 [file] [log] [blame]
/*
* 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.index;
import java.io.IOException;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import org.apache.lucene.search.FieldComparator;
import org.apache.lucene.search.SortField;
import org.apache.lucene.util.LongValues;
import org.apache.lucene.util.NumericUtils;
import org.apache.lucene.util.packed.PackedInts;
import static org.apache.lucene.search.DocIdSetIterator.NO_MORE_DOCS;
/**
* Handles how documents should be sorted in an index, both within a segment and between
* segments.
*
* Implementers must provide the following methods:
* {@link #getDocComparator(LeafReader,int)} - an object that determines how documents within a segment are to be sorted
* {@link #getComparableProviders(List)} - an array of objects that return a sortable long value per document and segment
* {@link #getProviderName()} - the SPI-registered name of a {@link SortFieldProvider} to serialize the sort
*
* The companion {@link SortFieldProvider} should be registered with SPI via {@code META-INF/services}
*/
public interface IndexSorter {
/** Used for sorting documents across segments */
interface ComparableProvider {
/**
* Returns a long so that the natural ordering of long values matches the
* ordering of doc IDs for the given comparator
*/
long getAsComparableLong(int docID) throws IOException;
}
/** A comparator of doc IDs, used for sorting documents within a segment */
interface DocComparator {
/** Compare docID1 against docID2. The contract for the return value is the
* same as {@link Comparator#compare(Object, Object)}. */
int compare(int docID1, int docID2);
}
/**
* Get an array of {@link ComparableProvider}, one per segment, for merge sorting documents in different segments
* @param readers the readers to be merged
*/
ComparableProvider[] getComparableProviders(List<? extends LeafReader> readers) throws IOException;
/**
* Get a comparator that determines the sort order of docs within a single Reader.
*
* NB We cannot simply use the {@link FieldComparator} API because it requires docIDs to be sent
* in-order. The default implementations allocate array[maxDoc] to hold native values for comparison,
* but 1) they are transient (only alive while sorting this one segment) and 2) in the typical
* index sorting case, they are only used to sort newly flushed segments, which will be smaller
* than merged segments
*
* @param reader the Reader to sort
* @param maxDoc the number of documents in the Reader
*/
DocComparator getDocComparator(LeafReader reader, int maxDoc) throws IOException;
/**
* The SPI-registered name of a {@link SortFieldProvider} that will deserialize the parent SortField
*/
String getProviderName();
/**
* Provide a NumericDocValues instance for a LeafReader
*/
interface NumericDocValuesProvider {
/**
* Returns the NumericDocValues instance for this LeafReader
*/
NumericDocValues get(LeafReader reader) throws IOException;
}
/**
* Provide a SortedDocValues instance for a LeafReader
*/
interface SortedDocValuesProvider {
/**
* Returns the SortedDocValues instance for this LeafReader
*/
SortedDocValues get(LeafReader reader) throws IOException;
}
/**
* Sorts documents based on integer values from a NumericDocValues instance
*/
final class IntSorter implements IndexSorter {
private final Integer missingValue;
private final int reverseMul;
private final NumericDocValuesProvider valuesProvider;
private final String providerName;
/**
* Creates a new IntSorter
*/
public IntSorter(String providerName, Integer missingValue, boolean reverse, NumericDocValuesProvider valuesProvider) {
this.missingValue = missingValue;
this.reverseMul = reverse ? -1 : 1;
this.valuesProvider = valuesProvider;
this.providerName = providerName;
}
@Override
public ComparableProvider[] getComparableProviders(List<? extends LeafReader> readers) throws IOException {
ComparableProvider[] providers = new ComparableProvider[readers.size()];
final long missingValue;
if (this.missingValue != null) {
missingValue = this.missingValue;
} else {
missingValue = 0L;
}
for(int readerIndex=0;readerIndex<readers.size();readerIndex++) {
final NumericDocValues values = valuesProvider.get(readers.get(readerIndex));
providers[readerIndex] = docID -> {
if (values.advanceExact(docID)) {
return values.longValue();
} else {
return missingValue;
}
};
}
return providers;
}
@Override
public DocComparator getDocComparator(LeafReader reader, int maxDoc) throws IOException {
final NumericDocValues dvs = valuesProvider.get(reader);
int[] values = new int[maxDoc];
if (this.missingValue != null) {
Arrays.fill(values, this.missingValue);
}
while (true) {
int docID = dvs.nextDoc();
if (docID == NO_MORE_DOCS) {
break;
}
values[docID] = (int) dvs.longValue();
}
return (docID1, docID2) -> reverseMul * Integer.compare(values[docID1], values[docID2]);
}
@Override
public String getProviderName() {
return providerName;
}
}
/**
* Sorts documents based on long values from a NumericDocValues instance
*/
final class LongSorter implements IndexSorter {
private final String providerName;
private final Long missingValue;
private final int reverseMul;
private final NumericDocValuesProvider valuesProvider;
/** Creates a new LongSorter */
public LongSorter(String providerName, Long missingValue, boolean reverse, NumericDocValuesProvider valuesProvider) {
this.providerName = providerName;
this.missingValue = missingValue;
this.reverseMul = reverse ? -1 : 1;
this.valuesProvider = valuesProvider;
}
@Override
public ComparableProvider[] getComparableProviders(List<? extends LeafReader> readers) throws IOException {
ComparableProvider[] providers = new ComparableProvider[readers.size()];
final long missingValue;
if (this.missingValue != null) {
missingValue = this.missingValue;
} else {
missingValue = 0L;
}
for(int readerIndex=0;readerIndex<readers.size();readerIndex++) {
final NumericDocValues values = valuesProvider.get(readers.get(readerIndex));
providers[readerIndex] = docID -> {
if (values.advanceExact(docID)) {
return values.longValue();
} else {
return missingValue;
}
};
}
return providers;
}
@Override
public DocComparator getDocComparator(LeafReader reader, int maxDoc) throws IOException {
final NumericDocValues dvs = valuesProvider.get(reader);
long[] values = new long[maxDoc];
if (this.missingValue != null) {
Arrays.fill(values, this.missingValue);
}
while (true) {
int docID = dvs.nextDoc();
if (docID == NO_MORE_DOCS) {
break;
}
values[docID] = dvs.longValue();
}
return (docID1, docID2) -> reverseMul * Long.compare(values[docID1], values[docID2]);
}
@Override
public String getProviderName() {
return providerName;
}
}
/**
* Sorts documents based on float values from a NumericDocValues instance
*/
final class FloatSorter implements IndexSorter {
private final String providerName;
private final Float missingValue;
private final int reverseMul;
private final NumericDocValuesProvider valuesProvider;
/** Creates a new FloatSorter */
public FloatSorter(String providerName, Float missingValue, boolean reverse, NumericDocValuesProvider valuesProvider) {
this.providerName = providerName;
this.missingValue = missingValue;
this.reverseMul = reverse ? -1 : 1;
this.valuesProvider = valuesProvider;
}
@Override
public ComparableProvider[] getComparableProviders(List<? extends LeafReader> readers) throws IOException {
ComparableProvider[] providers = new ComparableProvider[readers.size()];
final float missingValue;
if (this.missingValue != null) {
missingValue = this.missingValue;
} else {
missingValue = 0.0f;
}
for(int readerIndex=0;readerIndex<readers.size();readerIndex++) {
final NumericDocValues values = valuesProvider.get(readers.get(readerIndex));
providers[readerIndex] = docID -> {
float value = missingValue;
if (values.advanceExact(docID)) {
value = Float.intBitsToFloat((int) values.longValue());
}
return NumericUtils.floatToSortableInt(value);
};
}
return providers;
}
@Override
public DocComparator getDocComparator(LeafReader reader, int maxDoc) throws IOException {
final NumericDocValues dvs = valuesProvider.get(reader);
float[] values = new float[maxDoc];
if (this.missingValue != null) {
Arrays.fill(values, this.missingValue);
}
while (true) {
int docID = dvs.nextDoc();
if (docID == NO_MORE_DOCS) {
break;
}
values[docID] = Float.intBitsToFloat((int) dvs.longValue());
}
return (docID1, docID2) -> reverseMul * Float.compare(values[docID1], values[docID2]);
}
@Override
public String getProviderName() {
return providerName;
}
}
/**
* Sorts documents based on double values from a NumericDocValues instance
*/
final class DoubleSorter implements IndexSorter {
private final String providerName;
private final Double missingValue;
private final int reverseMul;
private final NumericDocValuesProvider valuesProvider;
/** Creates a new DoubleSorter */
public DoubleSorter(String providerName, Double missingValue, boolean reverse, NumericDocValuesProvider valuesProvider) {
this.providerName = providerName;
this.missingValue = missingValue;
this.reverseMul = reverse ? -1 : 1;
this.valuesProvider = valuesProvider;
}
@Override
public ComparableProvider[] getComparableProviders(List<? extends LeafReader> readers) throws IOException {
ComparableProvider[] providers = new ComparableProvider[readers.size()];
final double missingValue;
if (this.missingValue != null) {
missingValue = this.missingValue;
} else {
missingValue = 0.0f;
}
for(int readerIndex=0;readerIndex<readers.size();readerIndex++) {
final NumericDocValues values = valuesProvider.get(readers.get(readerIndex));
providers[readerIndex] = docID -> {
double value = missingValue;
if (values.advanceExact(docID)) {
value = Double.longBitsToDouble(values.longValue());
}
return NumericUtils.doubleToSortableLong(value);
};
}
return providers;
}
@Override
public DocComparator getDocComparator(LeafReader reader, int maxDoc) throws IOException {
final NumericDocValues dvs = valuesProvider.get(reader);
double[] values = new double[maxDoc];
if (missingValue != null) {
Arrays.fill(values, missingValue);
}
while (true) {
int docID = dvs.nextDoc();
if (docID == NO_MORE_DOCS) {
break;
}
values[docID] = Double.longBitsToDouble(dvs.longValue());
}
return (docID1, docID2) -> reverseMul * Double.compare(values[docID1], values[docID2]);
}
@Override
public String getProviderName() {
return providerName;
}
}
/**
* Sorts documents based on terms from a SortedDocValues instance
*/
final class StringSorter implements IndexSorter {
private final String providerName;
private final Object missingValue;
private final int reverseMul;
private final SortedDocValuesProvider valuesProvider;
/** Creates a new StringSorter */
public StringSorter(String providerName, Object missingValue, boolean reverse, SortedDocValuesProvider valuesProvider) {
this.providerName = providerName;
this.missingValue = missingValue;
this.reverseMul = reverse ? -1 : 1;
this.valuesProvider = valuesProvider;
}
@Override
public ComparableProvider[] getComparableProviders(List<? extends LeafReader> readers) throws IOException {
final ComparableProvider[] providers = new ComparableProvider[readers.size()];
final SortedDocValues[] values = new SortedDocValues[readers.size()];
for(int i=0;i<readers.size();i++) {
final SortedDocValues sorted = valuesProvider.get(readers.get(i));
values[i] = sorted;
}
OrdinalMap ordinalMap = OrdinalMap.build(null, values, PackedInts.DEFAULT);
final int missingOrd;
if (missingValue == SortField.STRING_LAST) {
missingOrd = Integer.MAX_VALUE;
} else {
missingOrd = Integer.MIN_VALUE;
}
for(int readerIndex=0;readerIndex<readers.size();readerIndex++) {
final SortedDocValues readerValues = values[readerIndex];
final LongValues globalOrds = ordinalMap.getGlobalOrds(readerIndex);
providers[readerIndex] = docID -> {
if (readerValues.advanceExact(docID)) {
// translate segment's ord to global ord space:
return globalOrds.get(readerValues.ordValue());
} else {
return missingOrd;
}
};
}
return providers;
}
@Override
public DocComparator getDocComparator(LeafReader reader, int maxDoc) throws IOException {
final SortedDocValues sorted = valuesProvider.get(reader);
final int missingOrd;
if (missingValue == SortField.STRING_LAST) {
missingOrd = Integer.MAX_VALUE;
} else {
missingOrd = Integer.MIN_VALUE;
}
final int[] ords = new int[maxDoc];
Arrays.fill(ords, missingOrd);
int docID;
while ((docID = sorted.nextDoc()) != NO_MORE_DOCS) {
ords[docID] = sorted.ordValue();
}
return (docID1, docID2) -> reverseMul * Integer.compare(ords[docID1], ords[docID2]);
}
@Override
public String getProviderName() {
return providerName;
}
}
}