Skip to content

Use IndexOrDocValuesQuery in NumberFieldType#termQuery implementations #128293

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
May 23, 2025
5 changes: 5 additions & 0 deletions docs/changelog/128293.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pr: 128293
summary: Use `IndexOrDocValuesQuery` in `NumberFieldType#termQuery` implementations
area: Search
type: enhancement
issues: []
Original file line number Diff line number Diff line change
@@ -323,7 +323,7 @@ public boolean isSearchable() {
public Query termQuery(Object value, SearchExecutionContext context) {
failIfNotIndexedNorDocValuesFallback(context);
long scaledValue = Math.round(scale(value));
return NumberFieldMapper.NumberType.LONG.termQuery(name(), scaledValue, isIndexed());
return NumberFieldMapper.NumberType.LONG.termQuery(name(), scaledValue, isIndexed(), hasDocValues());
}

@Override
Original file line number Diff line number Diff line change
@@ -11,6 +11,7 @@

import org.apache.lucene.document.Document;
import org.apache.lucene.document.DoublePoint;
import org.apache.lucene.document.LongField;
import org.apache.lucene.document.LongPoint;
import org.apache.lucene.document.SortedNumericDocValuesField;
import org.apache.lucene.index.DirectoryReader;
@@ -47,7 +48,7 @@ public void testTermQuery() {
);
double value = (randomDouble() * 2 - 1) * 10000;
long scaledValue = Math.round(value * ft.getScalingFactor());
assertEquals(LongPoint.newExactQuery("scaled_float", scaledValue), ft.termQuery(value, MOCK_CONTEXT));
assertEquals(LongField.newExactQuery("scaled_float", scaledValue), ft.termQuery(value, MOCK_CONTEXT));

MappedFieldType ft2 = new ScaledFloatFieldMapper.ScaledFloatFieldType("scaled_float", 0.1 + randomDouble() * 100, false);
ElasticsearchException e2 = expectThrows(ElasticsearchException.class, () -> ft2.termQuery("42", MOCK_CONTEXT_DISALLOW_EXPENSIVE));
Original file line number Diff line number Diff line change
@@ -351,13 +351,19 @@ public Float parse(XContentParser parser, boolean coerce) throws IOException {
}

@Override
public Query termQuery(String field, Object value, boolean isIndexed) {
public Query termQuery(String field, Object value, boolean isIndexed, boolean hasDocValues) {
float v = parseToFloat(value);
if (Float.isFinite(HalfFloatPoint.sortableShortToHalfFloat(HalfFloatPoint.halfFloatToSortableShort(v))) == false) {
return Queries.newMatchNoDocsQuery("Value [" + value + "] is out of range");
}

if (isIndexed) {
if (hasDocValues) {
return new IndexOrDocValuesQuery(
HalfFloatPoint.newExactQuery(field, v),
SortedNumericDocValuesField.newSlowExactQuery(field, HalfFloatPoint.halfFloatToSortableShort(v))
);
}
return HalfFloatPoint.newExactQuery(field, v);
} else {
return SortedNumericDocValuesField.newSlowExactQuery(field, HalfFloatPoint.halfFloatToSortableShort(v));
@@ -541,13 +547,15 @@ public Float parse(XContentParser parser, boolean coerce) throws IOException {
}

@Override
public Query termQuery(String field, Object value, boolean isIndexed) {
public Query termQuery(String field, Object value, boolean isIndexed, boolean hasDocValues) {
float v = parseToFloat(value);
if (Float.isFinite(v) == false) {
return new MatchNoDocsQuery("Value [" + value + "] is out of range");
}

if (isIndexed) {
if (isIndexed && hasDocValues) {
return FloatField.newExactQuery(field, v);
} else if (isIndexed) {
return FloatPoint.newExactQuery(field, v);
} else {
return SortedNumericDocValuesField.newSlowExactQuery(field, NumericUtils.floatToSortableInt(v));
@@ -714,13 +722,15 @@ public FieldValues<Number> compile(String fieldName, Script script, ScriptCompil
}

@Override
public Query termQuery(String field, Object value, boolean isIndexed) {
public Query termQuery(String field, Object value, boolean isIndexed, boolean hasDocValues) {
double v = objectToDouble(value);
if (Double.isFinite(v) == false) {
return Queries.newMatchNoDocsQuery("Value [" + value + "] has a decimal part");
}

if (isIndexed) {
if (isIndexed && hasDocValues) {
return DoubleField.newExactQuery(field, v);
} else if (isIndexed) {
return DoublePoint.newExactQuery(field, v);
} else {
return SortedNumericDocValuesField.newSlowExactQuery(field, NumericUtils.doubleToSortableLong(v));
@@ -874,12 +884,12 @@ public Byte parse(XContentParser parser, boolean coerce) throws IOException {
}

@Override
public Query termQuery(String field, Object value, boolean isIndexed) {
public Query termQuery(String field, Object value, boolean isIndexed, boolean hasDocValues) {
if (isOutOfRange(value)) {
return new MatchNoDocsQuery("Value [" + value + "] is out of range");
}

return INTEGER.termQuery(field, value, isIndexed);
return INTEGER.termQuery(field, value, isIndexed, hasDocValues);
}

@Override
@@ -998,11 +1008,11 @@ public Short parse(XContentParser parser, boolean coerce) throws IOException {
}

@Override
public Query termQuery(String field, Object value, boolean isIndexed) {
public Query termQuery(String field, Object value, boolean isIndexed, boolean hasDocValues) {
if (isOutOfRange(value)) {
return Queries.newMatchNoDocsQuery("Value [" + value + "] is out of range");
}
return INTEGER.termQuery(field, value, isIndexed);
return INTEGER.termQuery(field, value, isIndexed, hasDocValues);
}

@Override
@@ -1124,7 +1134,7 @@ public Integer parse(XContentParser parser, boolean coerce) throws IOException {
}

@Override
public Query termQuery(String field, Object value, boolean isIndexed) {
public Query termQuery(String field, Object value, boolean isIndexed, boolean hasDocValues) {
if (hasDecimalPart(value)) {
return Queries.newMatchNoDocsQuery("Value [" + value + "] has a decimal part");
}
@@ -1135,7 +1145,9 @@ public Query termQuery(String field, Object value, boolean isIndexed) {
}
int v = parse(value, true);

if (isIndexed) {
if (isIndexed && hasDocValues) {
return IntField.newExactQuery(field, v);
} else if (isIndexed) {
return IntPoint.newExactQuery(field, v);
} else {
return SortedNumericDocValuesField.newSlowExactQuery(field, v);
@@ -1308,7 +1320,7 @@ public FieldValues<Number> compile(String fieldName, Script script, ScriptCompil
}

@Override
public Query termQuery(String field, Object value, boolean isIndexed) {
public Query termQuery(String field, Object value, boolean isIndexed, boolean hasDocValues) {
if (hasDecimalPart(value)) {
return Queries.newMatchNoDocsQuery("Value [" + value + "] has a decimal part");
}
@@ -1317,7 +1329,9 @@ public Query termQuery(String field, Object value, boolean isIndexed) {
}

long v = parse(value, true);
if (isIndexed) {
if (isIndexed && hasDocValues) {
return LongField.newExactQuery(field, v);
} else if (isIndexed) {
return LongPoint.newExactQuery(field, v);
} else {
return SortedNumericDocValuesField.newSlowExactQuery(field, v);
@@ -1500,7 +1514,7 @@ public final TypeParser parser() {
return parser;
}

public abstract Query termQuery(String field, Object value, boolean isIndexed);
public abstract Query termQuery(String field, Object value, boolean isIndexed, boolean hasDocValues);

public abstract Query termsQuery(String field, Collection<?> values);

@@ -1891,11 +1905,11 @@ public NumberFieldType(
}

public NumberFieldType(String name, NumberType type) {
this(name, type, true);
this(name, type, true, true);
}

public NumberFieldType(String name, NumberType type, boolean isIndexed) {
this(name, type, isIndexed, false, true, true, null, Collections.emptyMap(), null, false, null, null, false);
public NumberFieldType(String name, NumberType type, boolean isIndexed, boolean hasDocValues) {
this(name, type, isIndexed, false, hasDocValues, true, null, Collections.emptyMap(), null, false, null, null, false);
}

@Override
@@ -1934,7 +1948,7 @@ public boolean isSearchable() {
@Override
public Query termQuery(Object value, SearchExecutionContext context) {
failIfNotIndexedNorDocValuesFallback(context);
return type.termQuery(name(), value, isIndexed());
return type.termQuery(name(), value, isIndexed(), hasDocValues());
}

@Override

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -11,6 +11,7 @@

import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.IndexOrDocValuesQuery;
import org.apache.lucene.search.IndexSortSortedNumericDocValuesRangeQuery;
import org.apache.lucene.search.MatchAllDocsQuery;
import org.apache.lucene.search.MatchNoDocsQuery;
import org.apache.lucene.search.PhraseQuery;
@@ -106,6 +107,7 @@ protected void doAssertLuceneQuery(MatchPhraseQueryBuilder queryBuilder, Query q
.or(instanceOf(PointRangeQuery.class))
.or(instanceOf(IndexOrDocValuesQuery.class))
.or(instanceOf(MatchNoDocsQuery.class))
.or(instanceOf(IndexSortSortedNumericDocValuesRangeQuery.class))
);
}

Original file line number Diff line number Diff line change
@@ -12,6 +12,8 @@
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.AutomatonQuery;
import org.apache.lucene.search.IndexOrDocValuesQuery;
import org.apache.lucene.search.IndexSortSortedNumericDocValuesRangeQuery;
import org.apache.lucene.search.MatchNoDocsQuery;
import org.apache.lucene.search.PointRangeQuery;
import org.apache.lucene.search.Query;
@@ -93,6 +95,8 @@ protected void doAssertLuceneQuery(TermQueryBuilder queryBuilder, Query query, S
either(instanceOf(TermQuery.class)).or(instanceOf(PointRangeQuery.class))
.or(instanceOf(MatchNoDocsQuery.class))
.or(instanceOf(AutomatonQuery.class))
.or(instanceOf(IndexOrDocValuesQuery.class))
.or(instanceOf(IndexSortSortedNumericDocValuesRangeQuery.class))
);
MappedFieldType mapper = context.getFieldType(queryBuilder.fieldName());
if (query instanceof TermQuery termQuery) {
Original file line number Diff line number Diff line change
@@ -582,8 +582,8 @@ public void testHeteroscedastic() throws IOException {

public void testFiltered() throws IOException {
TTestType tTestType = randomFrom(TTestType.values());
MappedFieldType fieldType1 = new NumberFieldMapper.NumberFieldType("a", NumberFieldMapper.NumberType.INTEGER);
MappedFieldType fieldType2 = new NumberFieldMapper.NumberFieldType("b", NumberFieldMapper.NumberType.INTEGER);
MappedFieldType fieldType1 = new NumberFieldMapper.NumberFieldType("a", NumberFieldMapper.NumberType.INTEGER, false, true);
MappedFieldType fieldType2 = new NumberFieldMapper.NumberFieldType("b", NumberFieldMapper.NumberType.INTEGER, true, false);
TTestAggregationBuilder aggregationBuilder = new TTestAggregationBuilder("t_test").a(
new MultiValuesSourceFieldConfig.Builder().setFieldName("a").setFilter(QueryBuilders.termQuery("b", 1)).build()
)
@@ -638,9 +638,9 @@ public void testFiltered() throws IOException {

public void testFilteredAsSubAgg() throws IOException {
TTestType tTestType = randomFrom(TTestType.values());
MappedFieldType fieldType1 = new NumberFieldMapper.NumberFieldType("h", NumberFieldMapper.NumberType.INTEGER);
MappedFieldType fieldType2 = new NumberFieldMapper.NumberFieldType("a", NumberFieldMapper.NumberType.INTEGER);
MappedFieldType fieldType3 = new NumberFieldMapper.NumberFieldType("b", NumberFieldMapper.NumberType.INTEGER);
MappedFieldType fieldType1 = new NumberFieldMapper.NumberFieldType("h", NumberFieldMapper.NumberType.INTEGER, false, true);
MappedFieldType fieldType2 = new NumberFieldMapper.NumberFieldType("a", NumberFieldMapper.NumberType.INTEGER, false, true);
MappedFieldType fieldType3 = new NumberFieldMapper.NumberFieldType("b", NumberFieldMapper.NumberType.INTEGER, true, false);
TTestAggregationBuilder ttestAggregationBuilder = new TTestAggregationBuilder("t_test").a(
new MultiValuesSourceFieldConfig.Builder().setFieldName("a").setFilter(QueryBuilders.termQuery("b", 1)).build()
)
@@ -711,7 +711,7 @@ public void testFilterByFilterOrScript() throws IOException {
boolean fieldInA = randomBoolean();
TTestType tTestType = randomFrom(TTestType.HOMOSCEDASTIC, TTestType.HETEROSCEDASTIC);

MappedFieldType fieldType1 = new NumberFieldMapper.NumberFieldType("field", NumberFieldMapper.NumberType.INTEGER);
MappedFieldType fieldType1 = new NumberFieldMapper.NumberFieldType("field", NumberFieldMapper.NumberType.INTEGER, false, true);
MappedFieldType fieldType2 = new NumberFieldMapper.NumberFieldType("term", NumberFieldMapper.NumberType.INTEGER);

boolean filterTermOne = randomBoolean();
Original file line number Diff line number Diff line change
@@ -151,7 +151,7 @@ public void testEqualityAndOther() throws IOException {
* single_value_match is here because there are extra documents hiding in the index
* that don't have the `foo` field.
*/
List.of("#foo:[1 TO 1] #single_value_match(foo)", "foo:[1 TO 1]");
List.of("#foo:[1 TO 1] #single_value_match(foo) #FieldExistsQuery [field=_primary_term]", "foo:[1 TO 1]");
default -> throw new UnsupportedOperationException("unknown type [" + type + "]");
};
boolean filterInCompute = switch (type) {
Original file line number Diff line number Diff line change
@@ -6,6 +6,7 @@
*/
package org.elasticsearch.xpack.aggregatemetric.mapper;

import org.apache.lucene.document.DoubleField;
import org.apache.lucene.document.DoublePoint;
import org.apache.lucene.document.NumericDocValuesField;
import org.apache.lucene.index.DirectoryReader;
@@ -65,7 +66,7 @@ protected AggregateMetricDoubleFieldType createDefaultFieldType(String name, Map
public void testTermQuery() {
final MappedFieldType fieldType = createDefaultFieldType("foo", Collections.emptyMap(), Metric.max);
Query query = fieldType.termQuery(55.2, MOCK_CONTEXT);
assertThat(query, equalTo(DoublePoint.newRangeQuery("foo.max", 55.2, 55.2)));
assertThat(query, equalTo(DoubleField.newRangeQuery("foo.max", 55.2, 55.2)));
}

public void testTermsQuery() {

Unchanged files with check annotations Beta

project: 'Elasticsearch'

Check notice on line 1 in docs/docset.yml

GitHub Actions / docs-preview / build

Substitution key 'ess-utm-params' is not used in any file

Check notice on line 1 in docs/docset.yml

GitHub Actions / docs-preview / build

Substitution key 'dfeed' is not used in any file

Check notice on line 1 in docs/docset.yml

GitHub Actions / docs-preview / build

Substitution key 'dataframe' is not used in any file

Check notice on line 1 in docs/docset.yml

GitHub Actions / docs-preview / build

Substitution key 'dataframe-cap' is not used in any file
products:
- id: elasticsearch
max_toc_depth: 2