LUCENE-10466: Ensure IndexSortSortedNumericDocValuesRangeQuery handles sort types besides LONG

IndexSortSortedNumericDocValuesRangeQuery unconditionally assumes the usage of
the LONG-encoded SortField. Using the numeric range query (in case of sorted
index) with anything but LONG ends up with class cast exception. Now the query
consults the numeric type of the `SortField` and perform appropriate checks.
This commit is contained in:
Andriy Redko 2022-04-04 15:02:00 -04:00 committed by GitHub
parent 0a525ce2ab
commit 737ce42c1c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 64 additions and 16 deletions

View File

@ -39,7 +39,9 @@ Optimizations
Bug Fixes
---------------------
(No changes)
* LUCENE-10466: Ensure IndexSortSortedNumericDocValuesRangeQuery handles sort field
types besides LONG (Andriy Redko)
Other
---------------------

View File

@ -40,6 +40,8 @@ import org.apache.lucene.search.Scorer;
import org.apache.lucene.search.ScorerSupplier;
import org.apache.lucene.search.Sort;
import org.apache.lucene.search.SortField;
import org.apache.lucene.search.SortField.Type;
import org.apache.lucene.search.SortedNumericSortField;
import org.apache.lucene.search.Weight;
/**
@ -52,6 +54,7 @@ import org.apache.lucene.search.Weight;
* <ul>
* <li>The index is sorted, and its primary sort is on the same field as the query.
* <li>The query field has either {@link SortedNumericDocValues} or {@link NumericDocValues}.
* <li>The sort field is of type {@code SortField.Type.LONG} or {@code SortField.Type.INT}.
* <li>The segments must have at most one field value per document (otherwise we cannot easily
* determine the matching document IDs through a binary search).
* </ul>
@ -222,8 +225,12 @@ public class IndexSortSortedNumericDocValuesRangeQuery extends Query {
&& indexSort.getSort().length > 0
&& indexSort.getSort()[0].getField().equals(field)) {
SortField sortField = indexSort.getSort()[0];
return getDocIdSetIterator(sortField, context, numericValues);
final SortField sortField = indexSort.getSort()[0];
final SortField.Type sortFieldType = getSortFieldType(sortField);
// The index sort optimization is only supported for Type.INT and Type.LONG
if (sortFieldType == Type.INT || sortFieldType == Type.LONG) {
return getDocIdSetIterator(sortField, sortFieldType, context, numericValues);
}
}
}
return null;
@ -242,14 +249,17 @@ public class IndexSortSortedNumericDocValuesRangeQuery extends Query {
* no value.
*/
private BoundedDocIdSetIterator getDocIdSetIterator(
SortField sortField, LeafReaderContext context, DocIdSetIterator delegate)
SortField sortField,
SortField.Type sortFieldType,
LeafReaderContext context,
DocIdSetIterator delegate)
throws IOException {
long lower = sortField.getReverse() ? upperValue : lowerValue;
long upper = sortField.getReverse() ? lowerValue : upperValue;
int maxDoc = context.reader().maxDoc();
// Perform a binary search to find the first document with value >= lower.
ValueComparator comparator = loadComparator(sortField, lower, context);
ValueComparator comparator = loadComparator(sortField, sortFieldType, lower, context);
int low = 0;
int high = maxDoc - 1;
@ -257,7 +267,7 @@ public class IndexSortSortedNumericDocValuesRangeQuery extends Query {
int mid = (low + high) >>> 1;
if (comparator.compare(mid) <= 0) {
high = mid - 1;
comparator = loadComparator(sortField, lower, context);
comparator = loadComparator(sortField, sortFieldType, lower, context);
} else {
low = mid + 1;
}
@ -267,7 +277,7 @@ public class IndexSortSortedNumericDocValuesRangeQuery extends Query {
// Perform a binary search to find the first document with value > upper.
// Since we know that upper >= lower, we can initialize the lower bound
// of the binary search to the result of the previous search.
comparator = loadComparator(sortField, upper, context);
comparator = loadComparator(sortField, sortFieldType, upper, context);
low = firstDocIdInclusive;
high = maxDoc - 1;
@ -275,7 +285,7 @@ public class IndexSortSortedNumericDocValuesRangeQuery extends Query {
int mid = (low + high) >>> 1;
if (comparator.compare(mid) < 0) {
high = mid - 1;
comparator = loadComparator(sortField, upper, context);
comparator = loadComparator(sortField, sortFieldType, upper, context);
} else {
low = mid + 1;
}
@ -303,11 +313,17 @@ public class IndexSortSortedNumericDocValuesRangeQuery extends Query {
}
private static ValueComparator loadComparator(
SortField sortField, long topValue, LeafReaderContext context) throws IOException {
SortField sortField, SortField.Type type, long topValue, LeafReaderContext context)
throws IOException {
@SuppressWarnings("unchecked")
FieldComparator<Long> fieldComparator =
(FieldComparator<Long>) sortField.getComparator(1, false);
fieldComparator.setTopValue(topValue);
FieldComparator<Number> fieldComparator =
(FieldComparator<Number>) sortField.getComparator(1, false);
if (type == Type.INT) {
fieldComparator.setTopValue((int) topValue);
} else {
// Since we support only Type.INT and Type.LONG, assuming LONG for all other cases
fieldComparator.setTopValue(topValue);
}
LeafFieldComparator leafFieldComparator = fieldComparator.getLeafComparator(context);
int direction = sortField.getReverse() ? -1 : 1;
@ -318,6 +334,15 @@ public class IndexSortSortedNumericDocValuesRangeQuery extends Query {
};
}
private static SortField.Type getSortFieldType(SortField sortField) {
// We expect the sortField to be SortedNumericSortField
if (sortField instanceof SortedNumericSortField) {
return ((SortedNumericSortField) sortField).getNumericType();
} else {
return sortField.getType();
}
}
/**
* A doc ID set iterator that wraps a delegate iterator and only returns doc IDs in the range
* [firstDocInclusive, lastDoc).

View File

@ -155,15 +155,18 @@ public class TestIndexSortSortedNumericDocValuesRangeQuery extends LuceneTestCas
}
public void testIndexSortDocValuesWithEvenLength() throws Exception {
testIndexSortDocValuesWithEvenLength(false);
testIndexSortDocValuesWithEvenLength(true);
for (SortField.Type type : new SortField.Type[] {SortField.Type.INT, SortField.Type.LONG}) {
testIndexSortDocValuesWithEvenLength(true, type);
testIndexSortDocValuesWithEvenLength(false, type);
}
}
public void testIndexSortDocValuesWithEvenLength(boolean reverse) throws Exception {
public void testIndexSortDocValuesWithEvenLength(boolean reverse, SortField.Type type)
throws Exception {
Directory dir = newDirectory();
IndexWriterConfig iwc = new IndexWriterConfig(new MockAnalyzer(random()));
Sort indexSort = new Sort(new SortedNumericSortField("field", SortField.Type.LONG, reverse));
Sort indexSort = new Sort(new SortedNumericSortField("field", type, reverse));
iwc.setIndexSort(indexSort);
RandomIndexWriter writer = new RandomIndexWriter(random(), dir, iwc);
@ -428,6 +431,24 @@ public class TestIndexSortSortedNumericDocValuesRangeQuery extends LuceneTestCas
dir.close();
}
public void testOtherSortTypes() throws Exception {
for (SortField.Type type : new SortField.Type[] {SortField.Type.FLOAT, SortField.Type.DOUBLE}) {
Directory dir = newDirectory();
IndexWriterConfig iwc = new IndexWriterConfig(new MockAnalyzer(random()));
Sort indexSort = new Sort(new SortedNumericSortField("field", type));
iwc.setIndexSort(indexSort);
RandomIndexWriter writer = new RandomIndexWriter(random(), dir, iwc);
writer.addDocument(createDocument("field", 0));
testIndexSortOptimizationDeactivated(writer);
writer.close();
dir.close();
}
}
/**
* Test that the index sort optimization is not activated when some documents have multiple
* values.