Skip to content

Commit f52e779

Browse files
authored
Fix casting of scaled_float in sorts (#57207) (#57385)
Previously we'd get a `ClassCastException` when you tried to use `numeric_type` on `scaled_float`. Oops! This cleans up the CCE and moves some code around so the casting actually works.
1 parent 07c76f2 commit f52e779

File tree

6 files changed

+233
-194
lines changed

6 files changed

+233
-194
lines changed

modules/mapper-extras/src/main/java/org/elasticsearch/index/mapper/ScaledFloatFieldMapper.java

Lines changed: 11 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,11 @@
2929
import org.apache.lucene.search.BoostQuery;
3030
import org.apache.lucene.search.DocValuesFieldExistsQuery;
3131
import org.apache.lucene.search.Query;
32-
import org.apache.lucene.search.SortField;
3332
import org.apache.lucene.search.TermQuery;
3433
import org.apache.lucene.util.BytesRef;
3534
import org.elasticsearch.common.Explicit;
36-
import org.elasticsearch.common.Nullable;
3735
import org.elasticsearch.common.settings.Setting;
3836
import org.elasticsearch.common.settings.Settings;
39-
import org.elasticsearch.common.util.BigArrays;
4037
import org.elasticsearch.common.xcontent.XContentBuilder;
4138
import org.elasticsearch.common.xcontent.XContentParser;
4239
import org.elasticsearch.common.xcontent.XContentParser.Token;
@@ -45,25 +42,20 @@
4542
import org.elasticsearch.index.IndexSettings;
4643
import org.elasticsearch.index.fielddata.FieldData;
4744
import org.elasticsearch.index.fielddata.IndexFieldData;
48-
import org.elasticsearch.index.fielddata.IndexFieldData.XFieldComparatorSource.Nested;
4945
import org.elasticsearch.index.fielddata.IndexFieldDataCache;
5046
import org.elasticsearch.index.fielddata.IndexNumericFieldData;
5147
import org.elasticsearch.index.fielddata.LeafNumericFieldData;
5248
import org.elasticsearch.index.fielddata.NumericDoubleValues;
5349
import org.elasticsearch.index.fielddata.ScriptDocValues;
5450
import org.elasticsearch.index.fielddata.SortedBinaryDocValues;
5551
import org.elasticsearch.index.fielddata.SortedNumericDoubleValues;
56-
import org.elasticsearch.index.fielddata.fieldcomparator.DoubleValuesComparatorSource;
5752
import org.elasticsearch.index.fielddata.plain.SortedNumericIndexFieldData;
5853
import org.elasticsearch.index.mapper.NumberFieldMapper.Defaults;
5954
import org.elasticsearch.index.query.QueryShardContext;
6055
import org.elasticsearch.indices.breaker.CircuitBreakerService;
6156
import org.elasticsearch.search.DocValueFormat;
62-
import org.elasticsearch.search.MultiValueMode;
6357
import org.elasticsearch.search.aggregations.support.CoreValuesSourceType;
6458
import org.elasticsearch.search.aggregations.support.ValuesSourceType;
65-
import org.elasticsearch.search.sort.BucketedSort;
66-
import org.elasticsearch.search.sort.SortOrder;
6759

6860
import java.io.IOException;
6961
import java.math.BigDecimal;
@@ -499,7 +491,7 @@ private static double objectToDouble(Object value) {
499491
return doubleValue;
500492
}
501493

502-
private static class ScaledFloatIndexFieldData implements IndexNumericFieldData {
494+
private static class ScaledFloatIndexFieldData extends IndexNumericFieldData {
503495

504496
private final IndexNumericFieldData scaledFieldData;
505497
private final double scalingFactor;
@@ -525,16 +517,15 @@ public LeafNumericFieldData loadDirect(LeafReaderContext context) throws Excepti
525517
}
526518

527519
@Override
528-
public SortField sortField(@Nullable Object missingValue, MultiValueMode sortMode, Nested nested, boolean reverse) {
529-
final XFieldComparatorSource source = new DoubleValuesComparatorSource(this, missingValue, sortMode, nested);
530-
return new SortField(getFieldName(), source, reverse);
531-
}
532-
533-
@Override
534-
public BucketedSort newBucketedSort(BigArrays bigArrays, Object missingValue, MultiValueMode sortMode, Nested nested,
535-
SortOrder sortOrder, DocValueFormat format, int bucketSize, BucketedSort.ExtraData extra) {
536-
return new DoubleValuesComparatorSource(this, missingValue, sortMode, nested)
537-
.newBucketedSort(bigArrays, sortOrder, format, bucketSize, extra);
520+
protected boolean sortRequiresCustomComparator() {
521+
/*
522+
* We need to use a custom comparator because the non-custom
523+
* comparator wouldn't properly decode the long bits into the
524+
* double. Sorting on the long representation *would* put the
525+
* docs in order. We just don't have a way to convert the long
526+
* into a double the right way afterwords.
527+
*/
528+
return true;
538529
}
539530

540531
@Override
@@ -549,7 +540,7 @@ public Index index() {
549540

550541
@Override
551542
public NumericType getNumericType() {
552-
/**
543+
/*
553544
* {@link ScaledFloatLeafFieldData#getDoubleValues()} transforms the raw long values in `scaled` floats.
554545
*/
555546
return NumericType.DOUBLE;

modules/mapper-extras/src/test/resources/rest-api-spec/test/scaled_float/10_basic.yml

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,28 @@ setup:
9797

9898
- do:
9999
search:
100-
rest_total_hits_as_int: true
101-
body: { "size" : 1, "sort" : { "number" : { "order" : "asc" } } }
100+
body:
101+
size: 1
102+
sort:
103+
number:
104+
order: asc
102105

103-
- match: { hits.total: 4 }
106+
- match: { hits.total.value: 4 }
107+
- match: { hits.hits.0._id: "3" }
108+
- match: { hits.hits.0.sort.0: -2.1 }
109+
110+
---
111+
"Sort with numeric_type":
112+
113+
- do:
114+
search:
115+
body:
116+
size: 1
117+
sort:
118+
number:
119+
order: asc
120+
numeric_type: long
121+
122+
- match: { hits.total.value: 4 }
104123
- match: { hits.hits.0._id: "3" }
124+
- match: { hits.hits.0.sort.0: -2 }

server/src/main/java/org/elasticsearch/index/fielddata/IndexNumericFieldData.java

Lines changed: 168 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -19,31 +19,184 @@
1919

2020
package org.elasticsearch.index.fielddata;
2121

22-
public interface IndexNumericFieldData extends IndexFieldData<LeafNumericFieldData> {
23-
24-
enum NumericType {
25-
BOOLEAN(false),
26-
BYTE(false),
27-
SHORT(false),
28-
INT(false),
29-
LONG(false),
30-
DATE(false),
31-
DATE_NANOSECONDS(false),
32-
HALF_FLOAT(true),
33-
FLOAT(true),
34-
DOUBLE(true);
22+
import org.apache.lucene.index.SortedNumericDocValues;
23+
import org.apache.lucene.search.SortField;
24+
import org.apache.lucene.search.SortedNumericSelector;
25+
import org.apache.lucene.search.SortedNumericSortField;
26+
import org.elasticsearch.common.Nullable;
27+
import org.elasticsearch.common.time.DateUtils;
28+
import org.elasticsearch.common.util.BigArrays;
29+
import org.elasticsearch.index.fielddata.IndexFieldData.XFieldComparatorSource.Nested;
30+
import org.elasticsearch.index.fielddata.fieldcomparator.DoubleValuesComparatorSource;
31+
import org.elasticsearch.index.fielddata.fieldcomparator.FloatValuesComparatorSource;
32+
import org.elasticsearch.index.fielddata.fieldcomparator.LongValuesComparatorSource;
33+
import org.elasticsearch.search.DocValueFormat;
34+
import org.elasticsearch.search.MultiValueMode;
35+
import org.elasticsearch.search.sort.BucketedSort;
36+
import org.elasticsearch.search.sort.SortOrder;
37+
38+
import java.io.IOException;
39+
import java.util.function.LongUnaryOperator;
40+
41+
/**
42+
* Base class for numeric field data.
43+
*/
44+
public abstract class IndexNumericFieldData implements IndexFieldData<LeafNumericFieldData> {
45+
/**
46+
* The type of number.
47+
*/
48+
public enum NumericType {
49+
BOOLEAN(false, SortField.Type.LONG),
50+
BYTE(false, SortField.Type.LONG),
51+
SHORT(false, SortField.Type.LONG),
52+
INT(false, SortField.Type.LONG),
53+
LONG(false, SortField.Type.LONG),
54+
DATE(false, SortField.Type.LONG),
55+
DATE_NANOSECONDS(false, SortField.Type.LONG),
56+
HALF_FLOAT(true, SortField.Type.LONG),
57+
FLOAT(true, SortField.Type.FLOAT),
58+
DOUBLE(true, SortField.Type.DOUBLE);
3559

3660
private final boolean floatingPoint;
61+
private final SortField.Type sortFieldType;
3762

38-
NumericType(boolean floatingPoint) {
63+
NumericType(boolean floatingPoint, SortField.Type sortFieldType) {
3964
this.floatingPoint = floatingPoint;
65+
this.sortFieldType = sortFieldType;
4066
}
4167

4268
public final boolean isFloatingPoint() {
4369
return floatingPoint;
4470
}
71+
}
72+
73+
/**
74+
* The numeric type of this number.
75+
*/
76+
public abstract NumericType getNumericType();
77+
78+
/**
79+
* Returns the {@link SortField} to used for sorting.
80+
* Values are casted to the provided <code>targetNumericType</code> type if it doesn't
81+
* match the field's <code>numericType</code>.
82+
*/
83+
public final SortField sortField(
84+
NumericType targetNumericType,
85+
Object missingValue,
86+
MultiValueMode sortMode,
87+
Nested nested,
88+
boolean reverse
89+
) {
90+
XFieldComparatorSource source = comparatorSource(targetNumericType, missingValue, sortMode, nested);
91+
92+
/*
93+
* Use a SortField with the custom comparator logic if required because
94+
* 1. The underlying data source needs it.
95+
* 2. We need to read the value from a nested field.
96+
* 3. We Aren't using max or min to resolve the duplicates.
97+
* 4. We have to cast the results to another type.
98+
*/
99+
if (sortRequiresCustomComparator()
100+
|| nested != null
101+
|| (sortMode != MultiValueMode.MAX && sortMode != MultiValueMode.MIN)
102+
|| targetNumericType != getNumericType()) {
103+
return new SortField(getFieldName(), source, reverse);
104+
}
105+
106+
SortedNumericSelector.Type selectorType = sortMode == MultiValueMode.MAX ?
107+
SortedNumericSelector.Type.MAX : SortedNumericSelector.Type.MIN;
108+
SortField sortField = new SortedNumericSortField(getFieldName(), getNumericType().sortFieldType, reverse, selectorType);
109+
sortField.setMissingValue(source.missingObject(missingValue, reverse));
110+
return sortField;
111+
}
112+
113+
/**
114+
* Does {@link #sortField} require a custom comparator because of the way
115+
* the data is stored in doc values ({@code true}) or are the docs values
116+
* stored such that they can be sorted without decoding ({@code false}).
117+
*/
118+
protected abstract boolean sortRequiresCustomComparator();
45119

120+
@Override
121+
public final SortField sortField(Object missingValue, MultiValueMode sortMode, Nested nested, boolean reverse) {
122+
return sortField(getNumericType(), missingValue, sortMode, nested, reverse);
46123
}
47124

48-
NumericType getNumericType();
125+
/**
126+
* Builds a {@linkplain BucketedSort} for the {@code targetNumericType},
127+
* casting the values if their native type doesn't match.
128+
*/
129+
public final BucketedSort newBucketedSort(NumericType targetNumericType, BigArrays bigArrays, @Nullable Object missingValue,
130+
MultiValueMode sortMode, Nested nested, SortOrder sortOrder, DocValueFormat format,
131+
int bucketSize, BucketedSort.ExtraData extra) {
132+
return comparatorSource(targetNumericType, missingValue, sortMode, nested)
133+
.newBucketedSort(bigArrays, sortOrder, format, bucketSize, extra);
134+
}
135+
136+
@Override
137+
public final BucketedSort newBucketedSort(BigArrays bigArrays, @Nullable Object missingValue, MultiValueMode sortMode, Nested nested,
138+
SortOrder sortOrder, DocValueFormat format, int bucketSize, BucketedSort.ExtraData extra) {
139+
return newBucketedSort(getNumericType(), bigArrays, missingValue, sortMode, nested, sortOrder, format, bucketSize, extra);
140+
}
141+
142+
/**
143+
* Build a {@link XFieldComparatorSource} matching the parameters.
144+
*/
145+
private XFieldComparatorSource comparatorSource(
146+
NumericType targetNumericType,
147+
@Nullable Object missingValue,
148+
MultiValueMode sortMode,
149+
Nested nested
150+
) {
151+
switch (targetNumericType) {
152+
case HALF_FLOAT:
153+
case FLOAT:
154+
return new FloatValuesComparatorSource(this, missingValue, sortMode, nested);
155+
case DOUBLE:
156+
return new DoubleValuesComparatorSource(this, missingValue, sortMode, nested);
157+
case DATE:
158+
return dateComparatorSource(missingValue, sortMode, nested);
159+
case DATE_NANOSECONDS:
160+
return dateNanosComparatorSource(missingValue, sortMode, nested);
161+
default:
162+
assert !targetNumericType.isFloatingPoint();
163+
return new LongValuesComparatorSource(this, missingValue, sortMode, nested);
164+
}
165+
}
166+
167+
protected XFieldComparatorSource dateComparatorSource(@Nullable Object missingValue, MultiValueMode sortMode, Nested nested) {
168+
return new LongValuesComparatorSource(this, missingValue, sortMode, nested);
169+
}
170+
171+
protected XFieldComparatorSource dateNanosComparatorSource(@Nullable Object missingValue, MultiValueMode sortMode, Nested nested) {
172+
return new LongValuesComparatorSource(this, missingValue, sortMode, nested, dvs -> convertNumeric(dvs, DateUtils::toNanoSeconds));
173+
}
174+
175+
/**
176+
* Convert the values in <code>dvs</code> using the provided <code>converter</code>.
177+
*/
178+
protected static SortedNumericDocValues convertNumeric(SortedNumericDocValues values, LongUnaryOperator converter) {
179+
return new AbstractSortedNumericDocValues() {
180+
181+
@Override
182+
public boolean advanceExact(int target) throws IOException {
183+
return values.advanceExact(target);
184+
}
185+
186+
@Override
187+
public long nextValue() throws IOException {
188+
return converter.applyAsLong(values.nextValue());
189+
}
190+
191+
@Override
192+
public int docValueCount() {
193+
return values.docValueCount();
194+
}
195+
196+
@Override
197+
public int nextDoc() throws IOException {
198+
return values.nextDoc();
199+
}
200+
};
201+
}
49202
}

0 commit comments

Comments
 (0)