| /* |
| * 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; |
| } |
| } |
| |
| } |