From 1ad83c37c4e28a9ea6cbb6a4f56b7ba4a8250b48 Mon Sep 17 00:00:00 2001 From: Julie Tibshirani Date: Wed, 13 May 2020 13:24:45 -0700 Subject: [PATCH] Use index sort range query when possible. (#56710) This PR proposes to use `IndexSortSortedNumericDocValuesRangeQuery` when possible to speed up certain range queries. Points-based queries are already very efficient, the only time this query makes a difference is when the range matches a large number of documents. Relates to #48665. --- .../index/mapper/ScaledFloatFieldMapper.java | 2 +- .../mapper/ScaledFloatFieldTypeTests.java | 30 +- .../percolator/CandidateQueryTests.java | 24 +- .../PercolatorFieldMapperTests.java | 7 +- .../elasticsearch/index/IndexSortConfig.java | 5 + .../index/mapper/DateFieldMapper.java | 7 + .../index/mapper/NumberFieldMapper.java | 49 ++-- .../index/query/QueryShardContext.java | 6 + .../index/mapper/DateFieldTypeTests.java | 35 ++- .../index/mapper/NumberFieldTypeTests.java | 269 ++++++++++++------ .../index/query/QueryShardContextTests.java | 22 ++ .../index/search/NestedHelperTests.java | 9 +- 12 files changed, 317 insertions(+), 148 deletions(-) diff --git a/modules/mapper-extras/src/main/java/org/elasticsearch/index/mapper/ScaledFloatFieldMapper.java b/modules/mapper-extras/src/main/java/org/elasticsearch/index/mapper/ScaledFloatFieldMapper.java index d95684b324b..69ecc1f1f47 100644 --- a/modules/mapper-extras/src/main/java/org/elasticsearch/index/mapper/ScaledFloatFieldMapper.java +++ b/modules/mapper-extras/src/main/java/org/elasticsearch/index/mapper/ScaledFloatFieldMapper.java @@ -275,7 +275,7 @@ public class ScaledFloatFieldMapper extends FieldMapper { } hi = Math.round(Math.floor(dValue)); } - Query query = NumberFieldMapper.NumberType.LONG.rangeQuery(name(), lo, hi, true, true, hasDocValues()); + Query query = NumberFieldMapper.NumberType.LONG.rangeQuery(name(), lo, hi, true, true, hasDocValues(), context); if (boost() != 1f) { query = new BoostQuery(query, boost()); } diff --git a/modules/mapper-extras/src/test/java/org/elasticsearch/index/mapper/ScaledFloatFieldTypeTests.java b/modules/mapper-extras/src/test/java/org/elasticsearch/index/mapper/ScaledFloatFieldTypeTests.java index 99afc1adc68..4de44a01ed6 100644 --- a/modules/mapper-extras/src/test/java/org/elasticsearch/index/mapper/ScaledFloatFieldTypeTests.java +++ b/modules/mapper-extras/src/test/java/org/elasticsearch/index/mapper/ScaledFloatFieldTypeTests.java @@ -117,8 +117,8 @@ public class ScaledFloatFieldTypeTests extends FieldTypeTestCase { Double u = randomBoolean() ? null : (randomDouble() * 2 - 1) * 10000; boolean includeLower = randomBoolean(); boolean includeUpper = randomBoolean(); - Query doubleQ = NumberFieldMapper.NumberType.DOUBLE.rangeQuery("double", l, u, includeLower, includeUpper, false); - Query scaledFloatQ = ft.rangeQuery(l, u, includeLower, includeUpper, null); + Query doubleQ = NumberFieldMapper.NumberType.DOUBLE.rangeQuery("double", l, u, includeLower, includeUpper, false, MOCK_QSC); + Query scaledFloatQ = ft.rangeQuery(l, u, includeLower, includeUpper, MOCK_QSC); assertEquals(searcher.count(doubleQ), searcher.count(scaledFloatQ)); } IOUtils.close(reader, dir); @@ -128,19 +128,19 @@ public class ScaledFloatFieldTypeTests extends FieldTypeTestCase { ScaledFloatFieldMapper.ScaledFloatFieldType ft = new ScaledFloatFieldMapper.ScaledFloatFieldType(); ft.setName("scaled_float"); ft.setScalingFactor(100.0); - Query scaledFloatQ = ft.rangeQuery(null, 0.1, true, false, null); + Query scaledFloatQ = ft.rangeQuery(null, 0.1, true, false, MOCK_QSC); assertEquals("scaled_float:[-9223372036854775808 TO 9]", scaledFloatQ.toString()); - scaledFloatQ = ft.rangeQuery(null, 0.1, true, true, null); + scaledFloatQ = ft.rangeQuery(null, 0.1, true, true, MOCK_QSC); assertEquals("scaled_float:[-9223372036854775808 TO 10]", scaledFloatQ.toString()); - scaledFloatQ = ft.rangeQuery(null, 0.095, true, false, null); + scaledFloatQ = ft.rangeQuery(null, 0.095, true, false, MOCK_QSC); assertEquals("scaled_float:[-9223372036854775808 TO 9]", scaledFloatQ.toString()); - scaledFloatQ = ft.rangeQuery(null, 0.095, true, true, null); + scaledFloatQ = ft.rangeQuery(null, 0.095, true, true, MOCK_QSC); assertEquals("scaled_float:[-9223372036854775808 TO 9]", scaledFloatQ.toString()); - scaledFloatQ = ft.rangeQuery(null, 0.105, true, false, null); + scaledFloatQ = ft.rangeQuery(null, 0.105, true, false, MOCK_QSC); assertEquals("scaled_float:[-9223372036854775808 TO 10]", scaledFloatQ.toString()); - scaledFloatQ = ft.rangeQuery(null, 0.105, true, true, null); + scaledFloatQ = ft.rangeQuery(null, 0.105, true, true, MOCK_QSC); assertEquals("scaled_float:[-9223372036854775808 TO 10]", scaledFloatQ.toString()); - scaledFloatQ = ft.rangeQuery(null, 79.99, true, true, null); + scaledFloatQ = ft.rangeQuery(null, 79.99, true, true, MOCK_QSC); assertEquals("scaled_float:[-9223372036854775808 TO 7999]", scaledFloatQ.toString()); } @@ -148,17 +148,17 @@ public class ScaledFloatFieldTypeTests extends FieldTypeTestCase { ScaledFloatFieldMapper.ScaledFloatFieldType ft = new ScaledFloatFieldMapper.ScaledFloatFieldType(); ft.setName("scaled_float"); ft.setScalingFactor(100.0); - Query scaledFloatQ = ft.rangeQuery(-0.1, null, false, true, null); + Query scaledFloatQ = ft.rangeQuery(-0.1, null, false, true, MOCK_QSC); assertEquals("scaled_float:[-9 TO 9223372036854775807]", scaledFloatQ.toString()); - scaledFloatQ = ft.rangeQuery(-0.1, null, true, true, null); + scaledFloatQ = ft.rangeQuery(-0.1, null, true, true, MOCK_QSC); assertEquals("scaled_float:[-10 TO 9223372036854775807]", scaledFloatQ.toString()); - scaledFloatQ = ft.rangeQuery(-0.095, null, false, true, null); + scaledFloatQ = ft.rangeQuery(-0.095, null, false, true, MOCK_QSC); assertEquals("scaled_float:[-9 TO 9223372036854775807]", scaledFloatQ.toString()); - scaledFloatQ = ft.rangeQuery(-0.095, null, true, true, null); + scaledFloatQ = ft.rangeQuery(-0.095, null, true, true, MOCK_QSC); assertEquals("scaled_float:[-9 TO 9223372036854775807]", scaledFloatQ.toString()); - scaledFloatQ = ft.rangeQuery(-0.105, null, false, true, null); + scaledFloatQ = ft.rangeQuery(-0.105, null, false, true, MOCK_QSC); assertEquals("scaled_float:[-10 TO 9223372036854775807]", scaledFloatQ.toString()); - scaledFloatQ = ft.rangeQuery(-0.105, null, true, true, null); + scaledFloatQ = ft.rangeQuery(-0.105, null, true, true, MOCK_QSC); assertEquals("scaled_float:[-10 TO 9223372036854775807]", scaledFloatQ.toString()); } diff --git a/modules/percolator/src/test/java/org/elasticsearch/percolator/CandidateQueryTests.java b/modules/percolator/src/test/java/org/elasticsearch/percolator/CandidateQueryTests.java index 980698d0171..287f0a2ddb9 100644 --- a/modules/percolator/src/test/java/org/elasticsearch/percolator/CandidateQueryTests.java +++ b/modules/percolator/src/test/java/org/elasticsearch/percolator/CandidateQueryTests.java @@ -95,6 +95,7 @@ import org.elasticsearch.index.mapper.MappedFieldType; import org.elasticsearch.index.mapper.MapperService; import org.elasticsearch.index.mapper.NumberFieldMapper; import org.elasticsearch.index.mapper.ParseContext; +import org.elasticsearch.index.query.QueryShardContext; import org.elasticsearch.plugins.Plugin; import org.elasticsearch.test.ESSingleNodeTestCase; import org.junit.After; @@ -123,6 +124,7 @@ public class CandidateQueryTests extends ESSingleNodeTestCase { private IndexWriter indexWriter; private DocumentMapper documentMapper; private DirectoryReader directoryReader; + private IndexService indexService; private MapperService mapperService; private PercolatorFieldMapper fieldMapper; @@ -144,7 +146,7 @@ public class CandidateQueryTests extends ESSingleNodeTestCase { indexWriter = new IndexWriter(directory, config); String indexName = "test"; - IndexService indexService = createIndex(indexName, Settings.EMPTY); + indexService = createIndex(indexName, Settings.EMPTY); mapperService = indexService.mapperService(); String mapper = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("type") @@ -197,6 +199,7 @@ public class CandidateQueryTests extends ESSingleNodeTestCase { } Collections.sort(intValues); + QueryShardContext context = createSearchContext(indexService).getQueryShardContext(); MappedFieldType intFieldType = mapperService.fieldType("int_field"); List> queryFunctions = new ArrayList<>(); @@ -207,10 +210,10 @@ public class CandidateQueryTests extends ESSingleNodeTestCase { queryFunctions.add(() -> new TermQuery(new Term(field1, randomFrom(stringContent.get(field1))))); String field2 = randomFrom(stringFields); queryFunctions.add(() -> new TermQuery(new Term(field2, randomFrom(stringContent.get(field2))))); - queryFunctions.add(() -> intFieldType.termQuery(randomFrom(intValues), null)); - queryFunctions.add(() -> intFieldType.termsQuery(Arrays.asList(randomFrom(intValues), randomFrom(intValues)), null)); + queryFunctions.add(() -> intFieldType.termQuery(randomFrom(intValues), context)); + queryFunctions.add(() -> intFieldType.termsQuery(Arrays.asList(randomFrom(intValues), randomFrom(intValues)), context)); queryFunctions.add(() -> intFieldType.rangeQuery(intValues.get(4), intValues.get(intValues.size() - 4), true, - true, ShapeRelation.WITHIN, null, null, null)); + true, ShapeRelation.WITHIN, null, null, context)); queryFunctions.add(() -> new TermInSetQuery(field1, new BytesRef(randomFrom(stringContent.get(field1))), new BytesRef(randomFrom(stringContent.get(field1))))); queryFunctions.add(() -> new TermInSetQuery(field2, new BytesRef(randomFrom(stringContent.get(field1))), @@ -335,6 +338,7 @@ public class CandidateQueryTests extends ESSingleNodeTestCase { ranges.add(new int[]{0, 10}); ranges.add(new int[]{15, 50}); + QueryShardContext context = createSearchContext(indexService).getQueryShardContext(); List documents = new ArrayList<>(); { addQuery(new TermQuery(new Term("string_field", randomFrom(stringValues))), documents); @@ -344,13 +348,13 @@ public class CandidateQueryTests extends ESSingleNodeTestCase { } { int[] range = randomFrom(ranges); - Query rangeQuery = intFieldType.rangeQuery(range[0], range[1], true, true, null, null, null, null); + Query rangeQuery = intFieldType.rangeQuery(range[0], range[1], true, true, null, null, null, context); addQuery(rangeQuery, documents); } { int numBooleanQueries = randomIntBetween(1, 5); for (int i = 0; i < numBooleanQueries; i++) { - Query randomBQ = randomBQ(1, stringValues, ranges, intFieldType); + Query randomBQ = randomBQ(1, stringValues, ranges, intFieldType, context); addQuery(randomBQ, documents); } } @@ -375,6 +379,7 @@ public class CandidateQueryTests extends ESSingleNodeTestCase { MemoryIndex memoryIndex = MemoryIndex.fromDocument(document, new WhitespaceAnalyzer()); duelRun(queryStore, memoryIndex, shardSearcher); } + for (int[] range : ranges) { List numberFields = NumberFieldMapper.NumberType.INTEGER.createFields("int_field", between(range[0], range[1]), true, true, false); @@ -387,7 +392,8 @@ public class CandidateQueryTests extends ESSingleNodeTestCase { } } - private BooleanQuery randomBQ(int depth, List stringValues, List ranges, MappedFieldType intFieldType) { + private BooleanQuery randomBQ(int depth, List stringValues, List ranges, + MappedFieldType intFieldType, QueryShardContext context) { final int numClauses = randomIntBetween(1, 4); final boolean onlyShouldClauses = randomBoolean(); final BooleanQuery.Builder builder = new BooleanQuery.Builder(); @@ -396,10 +402,10 @@ public class CandidateQueryTests extends ESSingleNodeTestCase { for (int i = 0; i < numClauses; i++) { Query subQuery; if (randomBoolean() && depth <= 3) { - subQuery = randomBQ(depth + 1, stringValues, ranges, intFieldType); + subQuery = randomBQ(depth + 1, stringValues, ranges, intFieldType, context); } else if (randomBoolean()) { int[] range = randomFrom(ranges); - subQuery = intFieldType.rangeQuery(range[0], range[1], true, true, null, null, null, null); + subQuery = intFieldType.rangeQuery(range[0], range[1], true, true, null, null, null, context); } else { subQuery = new TermQuery(new Term("string_field", randomFrom(stringValues))); } diff --git a/modules/percolator/src/test/java/org/elasticsearch/percolator/PercolatorFieldMapperTests.java b/modules/percolator/src/test/java/org/elasticsearch/percolator/PercolatorFieldMapperTests.java index 791bf183cca..fb12381bd6a 100644 --- a/modules/percolator/src/test/java/org/elasticsearch/percolator/PercolatorFieldMapperTests.java +++ b/modules/percolator/src/test/java/org/elasticsearch/percolator/PercolatorFieldMapperTests.java @@ -227,13 +227,14 @@ public class PercolatorFieldMapperTests extends ESSingleNodeTestCase { } public void testExtractRanges() throws Exception { + QueryShardContext context = createSearchContext(indexService).getQueryShardContext(); addQueryFieldMappings(); BooleanQuery.Builder bq = new BooleanQuery.Builder(); Query rangeQuery1 = mapperService.fieldType("number_field1") - .rangeQuery(10, 20, true, true, null, null, null, null); + .rangeQuery(10, 20, true, true, null, null, null, context); bq.add(rangeQuery1, Occur.MUST); Query rangeQuery2 = mapperService.fieldType("number_field1") - .rangeQuery(15, 20, true, true, null, null, null, null); + .rangeQuery(15, 20, true, true, null, null, null, context); bq.add(rangeQuery2, Occur.MUST); DocumentMapper documentMapper = mapperService.documentMapper("doc"); @@ -265,7 +266,7 @@ public class PercolatorFieldMapperTests extends ESSingleNodeTestCase { bq = new BooleanQuery.Builder(); bq.add(rangeQuery1, Occur.MUST); rangeQuery2 = mapperService.fieldType("number_field2") - .rangeQuery(15, 20, true, true, null, null, null, null); + .rangeQuery(15, 20, true, true, null, null, null, context); bq.add(rangeQuery2, Occur.MUST); parseContext = new ParseContext.InternalParseContext(settings, diff --git a/server/src/main/java/org/elasticsearch/index/IndexSortConfig.java b/server/src/main/java/org/elasticsearch/index/IndexSortConfig.java index a7fe1992876..68b7f504ed7 100644 --- a/server/src/main/java/org/elasticsearch/index/IndexSortConfig.java +++ b/server/src/main/java/org/elasticsearch/index/IndexSortConfig.java @@ -171,6 +171,11 @@ public final class IndexSortConfig { return sortSpecs.length > 0; } + public boolean hasPrimarySortOnField(String field) { + return sortSpecs.length > 0 + && sortSpecs[0].field.equals(field); + } + /** * Builds the {@link Sort} order from the settings for this index * or returns null if this index has no sort. diff --git a/server/src/main/java/org/elasticsearch/index/mapper/DateFieldMapper.java b/server/src/main/java/org/elasticsearch/index/mapper/DateFieldMapper.java index 581a108667b..579f0fe4a2d 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/DateFieldMapper.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/DateFieldMapper.java @@ -29,6 +29,7 @@ import org.apache.lucene.index.Term; import org.apache.lucene.search.BoostQuery; import org.apache.lucene.search.DocValuesFieldExistsQuery; import org.apache.lucene.search.IndexOrDocValuesQuery; +import org.apache.lucene.search.IndexSortSortedNumericDocValuesRangeQuery; import org.apache.lucene.search.Query; import org.apache.lucene.search.TermQuery; import org.apache.lucene.util.BytesRef; @@ -437,11 +438,17 @@ public final class DateFieldMapper extends FieldMapper { --u; } } + Query query = LongPoint.newRangeQuery(name(), l, u); if (hasDocValues()) { Query dvQuery = SortedNumericDocValuesField.newSlowRangeQuery(name(), l, u); query = new IndexOrDocValuesQuery(query, dvQuery); + + if (context.indexSortedOnField(name())) { + query = new IndexSortSortedNumericDocValuesRangeQuery(name(), l, u, query); + } } + if (nowUsed[0]) { query = new DateRangeIncludingNowQuery(query); } diff --git a/server/src/main/java/org/elasticsearch/index/mapper/NumberFieldMapper.java b/server/src/main/java/org/elasticsearch/index/mapper/NumberFieldMapper.java index bd1b93f79fe..8c1f4f94429 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/NumberFieldMapper.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/NumberFieldMapper.java @@ -34,6 +34,7 @@ import org.apache.lucene.index.Term; import org.apache.lucene.search.BoostQuery; import org.apache.lucene.search.DocValuesFieldExistsQuery; import org.apache.lucene.search.IndexOrDocValuesQuery; +import org.apache.lucene.search.IndexSortSortedNumericDocValuesRangeQuery; import org.apache.lucene.search.MatchNoDocsQuery; import org.apache.lucene.search.Query; import org.apache.lucene.search.TermQuery; @@ -218,8 +219,8 @@ public class NumberFieldMapper extends FieldMapper { @Override public Query rangeQuery(String field, Object lowerTerm, Object upperTerm, - boolean includeLower, boolean includeUpper, - boolean hasDocValues) { + boolean includeLower, boolean includeUpper, + boolean hasDocValues, QueryShardContext context) { float l = Float.NEGATIVE_INFINITY; float u = Float.POSITIVE_INFINITY; if (lowerTerm != null) { @@ -315,8 +316,8 @@ public class NumberFieldMapper extends FieldMapper { @Override public Query rangeQuery(String field, Object lowerTerm, Object upperTerm, - boolean includeLower, boolean includeUpper, - boolean hasDocValues) { + boolean includeLower, boolean includeUpper, + boolean hasDocValues, QueryShardContext context) { float l = Float.NEGATIVE_INFINITY; float u = Float.POSITIVE_INFINITY; if (lowerTerm != null) { @@ -334,8 +335,8 @@ public class NumberFieldMapper extends FieldMapper { Query query = FloatPoint.newRangeQuery(field, l, u); if (hasDocValues) { Query dvQuery = SortedNumericDocValuesField.newSlowRangeQuery(field, - NumericUtils.floatToSortableInt(l), - NumericUtils.floatToSortableInt(u)); + NumericUtils.floatToSortableInt(l), + NumericUtils.floatToSortableInt(u)); query = new IndexOrDocValuesQuery(query, dvQuery); } return query; @@ -401,8 +402,8 @@ public class NumberFieldMapper extends FieldMapper { @Override public Query rangeQuery(String field, Object lowerTerm, Object upperTerm, - boolean includeLower, boolean includeUpper, - boolean hasDocValues) { + boolean includeLower, boolean includeUpper, + boolean hasDocValues, QueryShardContext context) { double l = Double.NEGATIVE_INFINITY; double u = Double.POSITIVE_INFINITY; if (lowerTerm != null) { @@ -495,9 +496,9 @@ public class NumberFieldMapper extends FieldMapper { @Override public Query rangeQuery(String field, Object lowerTerm, Object upperTerm, - boolean includeLower, boolean includeUpper, - boolean hasDocValues) { - return INTEGER.rangeQuery(field, lowerTerm, upperTerm, includeLower, includeUpper, hasDocValues); + boolean includeLower, boolean includeUpper, + boolean hasDocValues, QueryShardContext context) { + return INTEGER.rangeQuery(field, lowerTerm, upperTerm, includeLower, includeUpper, hasDocValues, context); } @Override @@ -552,9 +553,9 @@ public class NumberFieldMapper extends FieldMapper { @Override public Query rangeQuery(String field, Object lowerTerm, Object upperTerm, - boolean includeLower, boolean includeUpper, - boolean hasDocValues) { - return INTEGER.rangeQuery(field, lowerTerm, upperTerm, includeLower, includeUpper, hasDocValues); + boolean includeLower, boolean includeUpper, + boolean hasDocValues, QueryShardContext context) { + return INTEGER.rangeQuery(field, lowerTerm, upperTerm, includeLower, includeUpper, hasDocValues, context); } @Override @@ -629,8 +630,8 @@ public class NumberFieldMapper extends FieldMapper { @Override public Query rangeQuery(String field, Object lowerTerm, Object upperTerm, - boolean includeLower, boolean includeUpper, - boolean hasDocValues) { + boolean includeLower, boolean includeUpper, + boolean hasDocValues, QueryShardContext context) { int l = Integer.MIN_VALUE; int u = Integer.MAX_VALUE; if (lowerTerm != null) { @@ -664,6 +665,9 @@ public class NumberFieldMapper extends FieldMapper { if (hasDocValues) { Query dvQuery = SortedNumericDocValuesField.newSlowRangeQuery(field, l, u); query = new IndexOrDocValuesQuery(query, dvQuery); + if (context.indexSortedOnField(field)) { + query = new IndexSortSortedNumericDocValuesRangeQuery(field, l, u, query); + } } return query; } @@ -748,8 +752,8 @@ public class NumberFieldMapper extends FieldMapper { @Override public Query rangeQuery(String field, Object lowerTerm, Object upperTerm, - boolean includeLower, boolean includeUpper, - boolean hasDocValues) { + boolean includeLower, boolean includeUpper, + boolean hasDocValues, QueryShardContext context) { long l = Long.MIN_VALUE; long u = Long.MAX_VALUE; if (lowerTerm != null) { @@ -783,6 +787,9 @@ public class NumberFieldMapper extends FieldMapper { if (hasDocValues) { Query dvQuery = SortedNumericDocValuesField.newSlowRangeQuery(field, l, u); query = new IndexOrDocValuesQuery(query, dvQuery); + if (context.indexSortedOnField(field)) { + query = new IndexSortSortedNumericDocValuesRangeQuery(field, l, u, query); + } } return query; } @@ -823,8 +830,8 @@ public class NumberFieldMapper extends FieldMapper { public abstract Query termQuery(String field, Object value); public abstract Query termsQuery(String field, List values); public abstract Query rangeQuery(String field, Object lowerTerm, Object upperTerm, - boolean includeLower, boolean includeUpper, - boolean hasDocValues); + boolean includeLower, boolean includeUpper, + boolean hasDocValues, QueryShardContext context); public abstract Number parse(XContentParser parser, boolean coerce) throws IOException; public abstract Number parse(Object value, boolean coerce); public abstract Number parsePoint(byte[] value); @@ -946,7 +953,7 @@ public class NumberFieldMapper extends FieldMapper { @Override public Query rangeQuery(Object lowerTerm, Object upperTerm, boolean includeLower, boolean includeUpper, QueryShardContext context) { failIfNotIndexed(); - Query query = type.rangeQuery(name(), lowerTerm, upperTerm, includeLower, includeUpper, hasDocValues()); + Query query = type.rangeQuery(name(), lowerTerm, upperTerm, includeLower, includeUpper, hasDocValues(), context); if (boost() != 1f) { query = new BoostQuery(query, boost()); } diff --git a/server/src/main/java/org/elasticsearch/index/query/QueryShardContext.java b/server/src/main/java/org/elasticsearch/index/query/QueryShardContext.java index 7e1900dcb01..b8a7ca64bdf 100644 --- a/server/src/main/java/org/elasticsearch/index/query/QueryShardContext.java +++ b/server/src/main/java/org/elasticsearch/index/query/QueryShardContext.java @@ -41,6 +41,7 @@ import org.elasticsearch.common.xcontent.NamedXContentRegistry; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.index.Index; import org.elasticsearch.index.IndexSettings; +import org.elasticsearch.index.IndexSortConfig; import org.elasticsearch.index.analysis.IndexAnalyzers; import org.elasticsearch.index.cache.bitset.BitsetFilterCache; import org.elasticsearch.index.fielddata.IndexFieldData; @@ -348,6 +349,11 @@ public class QueryShardContext extends QueryRewriteContext { return indexNameMatcher.test(pattern); } + public boolean indexSortedOnField(String field) { + IndexSortConfig indexSortConfig = indexSettings.getIndexSortConfig(); + return indexSortConfig.hasPrimarySortOnField(field); + } + public ParsedQuery toQuery(QueryBuilder queryBuilder) { return toQuery(queryBuilder, q -> { Query query = q.toQuery(this); diff --git a/server/src/test/java/org/elasticsearch/index/mapper/DateFieldTypeTests.java b/server/src/test/java/org/elasticsearch/index/mapper/DateFieldTypeTests.java index 255e79a4f89..e06b13caa82 100644 --- a/server/src/test/java/org/elasticsearch/index/mapper/DateFieldTypeTests.java +++ b/server/src/test/java/org/elasticsearch/index/mapper/DateFieldTypeTests.java @@ -30,6 +30,7 @@ import org.apache.lucene.index.MultiReader; import org.apache.lucene.index.SortedNumericDocValues; import org.apache.lucene.search.DocIdSetIterator; import org.apache.lucene.search.IndexOrDocValuesQuery; +import org.apache.lucene.search.IndexSortSortedNumericDocValuesRangeQuery; import org.apache.lucene.search.Query; import org.apache.lucene.store.Directory; import org.elasticsearch.Version; @@ -41,9 +42,9 @@ import org.elasticsearch.common.time.DateMathParser; import org.elasticsearch.common.util.BigArrays; import org.elasticsearch.core.internal.io.IOUtils; import org.elasticsearch.index.IndexSettings; -import org.elasticsearch.index.fielddata.LeafNumericFieldData; import org.elasticsearch.index.fielddata.IndexNumericFieldData; import org.elasticsearch.index.fielddata.plain.SortedNumericDVIndexFieldData; +import org.elasticsearch.index.fielddata.LeafNumericFieldData; import org.elasticsearch.index.mapper.DateFieldMapper.DateFieldType; import org.elasticsearch.index.mapper.DateFieldMapper.Resolution; import org.elasticsearch.index.mapper.MappedFieldType.Relation; @@ -285,6 +286,38 @@ public class DateFieldTypeTests extends FieldTypeTestCase { assertEquals("Cannot search on field [field] since it is not indexed.", e.getMessage()); } + public void testRangeQueryWithIndexSort() { + Settings settings = Settings.builder() + .put(IndexMetadata.SETTING_VERSION_CREATED, Version.CURRENT) + .put(IndexMetadata.SETTING_NUMBER_OF_SHARDS, 1) + .put(IndexMetadata.SETTING_NUMBER_OF_REPLICAS, 1) + .put("index.sort.field", "field") + .build(); + + IndexMetadata indexMetadata = new IndexMetadata.Builder("index") + .settings(settings) + .build(); + IndexSettings indexSettings = new IndexSettings(indexMetadata, settings); + + QueryShardContext context = new QueryShardContext(0, indexSettings, + BigArrays.NON_RECYCLING_INSTANCE, null, null, null, null, null, xContentRegistry(), writableRegistry(), + null, null, () -> 0L, null, null, () -> true, null); + + MappedFieldType ft = createDefaultFieldType(); + ft.setName("field"); + String date1 = "2015-10-12T14:10:55"; + String date2 = "2016-04-28T11:33:52"; + long instant1 = DateFormatters.from(DateFieldMapper.DEFAULT_DATE_TIME_FORMATTER.parse(date1)).toInstant().toEpochMilli(); + long instant2 = DateFormatters.from(DateFieldMapper.DEFAULT_DATE_TIME_FORMATTER.parse(date2)).toInstant().toEpochMilli() + 999; + ft.setIndexOptions(IndexOptions.DOCS); + + Query pointQuery = LongPoint.newRangeQuery("field", instant1, instant2); + Query dvQuery = SortedNumericDocValuesField.newSlowRangeQuery("field", instant1, instant2); + Query expected = new IndexSortSortedNumericDocValuesRangeQuery("field", instant1, instant2, + new IndexOrDocValuesQuery(pointQuery, dvQuery)); + assertEquals(expected, ft.rangeQuery(date1, date2, true, true, null, null, null, context)); + } + public void testDateNanoDocValues() throws IOException { // Create an index with some docValues Directory dir = newDirectory(); diff --git a/server/src/test/java/org/elasticsearch/index/mapper/NumberFieldTypeTests.java b/server/src/test/java/org/elasticsearch/index/mapper/NumberFieldTypeTests.java index b0704ce2040..884c7ddc293 100644 --- a/server/src/test/java/org/elasticsearch/index/mapper/NumberFieldTypeTests.java +++ b/server/src/test/java/org/elasticsearch/index/mapper/NumberFieldTypeTests.java @@ -33,16 +33,26 @@ import org.apache.lucene.index.IndexWriter; import org.apache.lucene.index.IndexWriterConfig; import org.apache.lucene.search.IndexOrDocValuesQuery; import org.apache.lucene.search.IndexSearcher; +import org.apache.lucene.search.IndexSortSortedNumericDocValuesRangeQuery; import org.apache.lucene.search.MatchNoDocsQuery; import org.apache.lucene.search.Query; +import org.apache.lucene.search.Sort; +import org.apache.lucene.search.SortField; import org.apache.lucene.store.Directory; import org.apache.lucene.util.BytesRef; import org.apache.lucene.util.TestUtil; +import org.elasticsearch.Version; +import org.elasticsearch.cluster.metadata.IndexMetadata; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.util.BigArrays; import org.elasticsearch.core.internal.io.IOUtils; +import org.elasticsearch.index.IndexSettings; +import org.elasticsearch.index.fielddata.IndexNumericFieldData; import org.elasticsearch.index.mapper.MappedFieldType.Relation; -import org.elasticsearch.index.mapper.NumberFieldMapper.NumberType; import org.elasticsearch.index.mapper.NumberFieldMapper.NumberFieldType; -import org.hamcrest.Matchers; +import org.elasticsearch.index.mapper.NumberFieldMapper.NumberType; +import org.elasticsearch.index.query.QueryShardContext; +import org.elasticsearch.search.MultiValueMode; import org.junit.Before; import java.io.IOException; @@ -55,6 +65,7 @@ import java.util.function.Supplier; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.instanceOf; public class NumberFieldTypeTests extends FieldTypeTestCase { @@ -150,116 +161,116 @@ public class NumberFieldTypeTests extends FieldTypeTestCase { MappedFieldType ftInt = new NumberFieldMapper.NumberFieldType(NumberType.INTEGER); ftInt.setName("field"); ftInt.setIndexOptions(IndexOptions.DOCS); - assertEquals(ftInt.rangeQuery(-3, -3, true, true, null, null, null, null), - ftInt.rangeQuery(-3.5, -2.5, true, true, null, null, null, null)); - assertEquals(ftInt.rangeQuery(-3, -3, true, true, null, null, null, null), - ftInt.rangeQuery(-3.5, -2.5, false, false, null, null, null, null)); - assertEquals(ftInt.rangeQuery(0, 0, true, true, null, null, null, null), - ftInt.rangeQuery(-0.5, 0.5, true, true, null, null, null, null)); - assertEquals(ftInt.rangeQuery(0, 0, true, true, null, null, null, null), - ftInt.rangeQuery(-0.5, 0.5, false, false, null, null, null, null)); - assertEquals(ftInt.rangeQuery(1, 2, true, true, null, null, null, null), - ftInt.rangeQuery(0.5, 2.5, true, true, null, null, null, null)); - assertEquals(ftInt.rangeQuery(1, 2, true, true, null, null, null, null), - ftInt.rangeQuery(0.5, 2.5, false, false, null, null, null, null)); - assertEquals(ftInt.rangeQuery(0, 2, true, true, null, null, null, null), - ftInt.rangeQuery(-0.5, 2.5, true, true, null, null, null, null)); - assertEquals(ftInt.rangeQuery(0, 2, true, true, null, null, null, null), - ftInt.rangeQuery(-0.5, 2.5, false, false, null, null, null, null)); + assertEquals(ftInt.rangeQuery(-3, -3, true, true, null, null, null, MOCK_QSC), + ftInt.rangeQuery(-3.5, -2.5, true, true, null, null, null, MOCK_QSC)); + assertEquals(ftInt.rangeQuery(-3, -3, true, true, null, null, null, MOCK_QSC), + ftInt.rangeQuery(-3.5, -2.5, false, false, null, null, null, MOCK_QSC)); + assertEquals(ftInt.rangeQuery(0, 0, true, true, null, null, null, MOCK_QSC), + ftInt.rangeQuery(-0.5, 0.5, true, true, null, null, null, MOCK_QSC)); + assertEquals(ftInt.rangeQuery(0, 0, true, true, null, null, null, MOCK_QSC), + ftInt.rangeQuery(-0.5, 0.5, false, false, null, null, null, MOCK_QSC)); + assertEquals(ftInt.rangeQuery(1, 2, true, true, null, null, null, MOCK_QSC), + ftInt.rangeQuery(0.5, 2.5, true, true, null, null, null, MOCK_QSC)); + assertEquals(ftInt.rangeQuery(1, 2, true, true, null, null, null, MOCK_QSC), + ftInt.rangeQuery(0.5, 2.5, false, false, null, null, null, MOCK_QSC)); + assertEquals(ftInt.rangeQuery(0, 2, true, true, null, null, null, MOCK_QSC), + ftInt.rangeQuery(-0.5, 2.5, true, true, null, null, null, MOCK_QSC)); + assertEquals(ftInt.rangeQuery(0, 2, true, true, null, null, null, MOCK_QSC), + ftInt.rangeQuery(-0.5, 2.5, false, false, null, null, null, MOCK_QSC)); - assertEquals(ftInt.rangeQuery(-2, 0, true, true, null, null, null, null), - ftInt.rangeQuery(-2.5, 0.5, true, true, null, null, null, null)); - assertEquals(ftInt.rangeQuery(-2, 0, true, true, null, null, null, null), - ftInt.rangeQuery(-2.5, 0.5, false, false, null, null, null, null)); - assertEquals(ftInt.rangeQuery(-2, -1, true, true, null, null, null, null), - ftInt.rangeQuery(-2.5, -0.5, true, true, null, null, null, null)); - assertEquals(ftInt.rangeQuery(-2, -1, true, true, null, null, null, null), - ftInt.rangeQuery(-2.5, -0.5, false, false, null, null, null, null)); + assertEquals(ftInt.rangeQuery(-2, 0, true, true, null, null, null, MOCK_QSC), + ftInt.rangeQuery(-2.5, 0.5, true, true, null, null, null, MOCK_QSC)); + assertEquals(ftInt.rangeQuery(-2, 0, true, true, null, null, null, MOCK_QSC), + ftInt.rangeQuery(-2.5, 0.5, false, false, null, null, null, MOCK_QSC)); + assertEquals(ftInt.rangeQuery(-2, -1, true, true, null, null, null, MOCK_QSC), + ftInt.rangeQuery(-2.5, -0.5, true, true, null, null, null, MOCK_QSC)); + assertEquals(ftInt.rangeQuery(-2, -1, true, true, null, null, null, MOCK_QSC), + ftInt.rangeQuery(-2.5, -0.5, false, false, null, null, null, MOCK_QSC)); MappedFieldType ftLong = new NumberFieldMapper.NumberFieldType(NumberType.LONG); ftLong.setName("field"); ftLong.setIndexOptions(IndexOptions.DOCS); - assertEquals(ftLong.rangeQuery(-3, -3, true, true, null, null, null, null), - ftLong.rangeQuery(-3.5, -2.5, true, true, null, null, null, null)); - assertEquals(ftLong.rangeQuery(-3, -3, true, true, null, null, null, null), - ftLong.rangeQuery(-3.5, -2.5, false, false, null, null, null, null)); - assertEquals(ftLong.rangeQuery(0, 0, true, true, null, null, null, null), - ftLong.rangeQuery(-0.5, 0.5, true, true, null, null, null, null)); - assertEquals(ftLong.rangeQuery(0, 0, true, true, null, null, null, null), - ftLong.rangeQuery(-0.5, 0.5, false, false, null, null, null, null)); - assertEquals(ftLong.rangeQuery(1, 2, true, true, null, null, null, null), - ftLong.rangeQuery(0.5, 2.5, true, true, null, null, null, null)); - assertEquals(ftLong.rangeQuery(1, 2, true, true, null, null, null, null), - ftLong.rangeQuery(0.5, 2.5, false, false, null, null, null, null)); - assertEquals(ftLong.rangeQuery(0, 2, true, true, null, null, null, null), - ftLong.rangeQuery(-0.5, 2.5, true, true, null, null, null, null)); - assertEquals(ftLong.rangeQuery(0, 2, true, true, null, null, null, null), - ftLong.rangeQuery(-0.5, 2.5, false, false, null, null, null, null)); + assertEquals(ftLong.rangeQuery(-3, -3, true, true, null, null, null, MOCK_QSC), + ftLong.rangeQuery(-3.5, -2.5, true, true, null, null, null, MOCK_QSC)); + assertEquals(ftLong.rangeQuery(-3, -3, true, true, null, null, null, MOCK_QSC), + ftLong.rangeQuery(-3.5, -2.5, false, false, null, null, null, MOCK_QSC)); + assertEquals(ftLong.rangeQuery(0, 0, true, true, null, null, null, MOCK_QSC), + ftLong.rangeQuery(-0.5, 0.5, true, true, null, null, null, MOCK_QSC)); + assertEquals(ftLong.rangeQuery(0, 0, true, true, null, null, null, MOCK_QSC), + ftLong.rangeQuery(-0.5, 0.5, false, false, null, null, null, MOCK_QSC)); + assertEquals(ftLong.rangeQuery(1, 2, true, true, null, null, null, MOCK_QSC), + ftLong.rangeQuery(0.5, 2.5, true, true, null, null, null, MOCK_QSC)); + assertEquals(ftLong.rangeQuery(1, 2, true, true, null, null, null, MOCK_QSC), + ftLong.rangeQuery(0.5, 2.5, false, false, null, null, null, MOCK_QSC)); + assertEquals(ftLong.rangeQuery(0, 2, true, true, null, null, null, MOCK_QSC), + ftLong.rangeQuery(-0.5, 2.5, true, true, null, null, null, MOCK_QSC)); + assertEquals(ftLong.rangeQuery(0, 2, true, true, null, null, null, MOCK_QSC), + ftLong.rangeQuery(-0.5, 2.5, false, false, null, null, null, MOCK_QSC)); - assertEquals(ftLong.rangeQuery(-2, 0, true, true, null, null, null, null), - ftLong.rangeQuery(-2.5, 0.5, true, true, null, null, null, null)); - assertEquals(ftLong.rangeQuery(-2, 0, true, true, null, null, null, null), - ftLong.rangeQuery(-2.5, 0.5, false, false, null, null, null, null)); - assertEquals(ftLong.rangeQuery(-2, -1, true, true, null, null, null, null), - ftLong.rangeQuery(-2.5, -0.5, true, true, null, null, null, null)); - assertEquals(ftLong.rangeQuery(-2, -1, true, true, null, null, null, null), - ftLong.rangeQuery(-2.5, -0.5, false, false, null, null, null, null)); + assertEquals(ftLong.rangeQuery(-2, 0, true, true, null, null, null, MOCK_QSC), + ftLong.rangeQuery(-2.5, 0.5, true, true, null, null, null, MOCK_QSC)); + assertEquals(ftLong.rangeQuery(-2, 0, true, true, null, null, null, MOCK_QSC), + ftLong.rangeQuery(-2.5, 0.5, false, false, null, null, null, MOCK_QSC)); + assertEquals(ftLong.rangeQuery(-2, -1, true, true, null, null, null, MOCK_QSC), + ftLong.rangeQuery(-2.5, -0.5, true, true, null, null, null, MOCK_QSC)); + assertEquals(ftLong.rangeQuery(-2, -1, true, true, null, null, null, MOCK_QSC), + ftLong.rangeQuery(-2.5, -0.5, false, false, null, null, null, MOCK_QSC)); } public void testByteRangeQueryWithDecimalParts() { MappedFieldType ft = new NumberFieldMapper.NumberFieldType(NumberType.BYTE); ft.setName("field"); ft.setIndexOptions(IndexOptions.DOCS); - assertEquals(ft.rangeQuery(2, 10, true, true, null, null, null, null), - ft.rangeQuery(1.1, 10, true, true, null, null, null, null)); - assertEquals(ft.rangeQuery(2, 10, true, true, null, null, null, null), - ft.rangeQuery(1.1, 10, false, true, null, null, null, null)); - assertEquals(ft.rangeQuery(1, 10, true, true, null, null, null, null), - ft.rangeQuery(1, 10.1, true, true, null, null, null, null)); - assertEquals(ft.rangeQuery(1, 10, true, true, null, null, null, null), - ft.rangeQuery(1, 10.1, true, false, null, null, null, null)); + assertEquals(ft.rangeQuery(2, 10, true, true, null, null, null, MOCK_QSC), + ft.rangeQuery(1.1, 10, true, true, null, null, null, MOCK_QSC)); + assertEquals(ft.rangeQuery(2, 10, true, true, null, null, null, MOCK_QSC), + ft.rangeQuery(1.1, 10, false, true, null, null, null, MOCK_QSC)); + assertEquals(ft.rangeQuery(1, 10, true, true, null, null, null, MOCK_QSC), + ft.rangeQuery(1, 10.1, true, true, null, null, null, MOCK_QSC)); + assertEquals(ft.rangeQuery(1, 10, true, true, null, null, null, MOCK_QSC), + ft.rangeQuery(1, 10.1, true, false, null, null, null, MOCK_QSC)); } public void testShortRangeQueryWithDecimalParts() { MappedFieldType ft = new NumberFieldMapper.NumberFieldType(NumberType.SHORT); ft.setName("field"); ft.setIndexOptions(IndexOptions.DOCS); - assertEquals(ft.rangeQuery(2, 10, true, true, null, null, null, null), - ft.rangeQuery(1.1, 10, true, true, null, null, null, null)); - assertEquals(ft.rangeQuery(2, 10, true, true, null, null, null, null), - ft.rangeQuery(1.1, 10, false, true, null, null, null, null)); - assertEquals(ft.rangeQuery(1, 10, true, true, null, null, null, null), - ft.rangeQuery(1, 10.1, true, true, null, null, null, null)); - assertEquals(ft.rangeQuery(1, 10, true, true, null, null, null, null), - ft.rangeQuery(1, 10.1, true, false, null, null, null, null)); + assertEquals(ft.rangeQuery(2, 10, true, true, null, null, null, MOCK_QSC), + ft.rangeQuery(1.1, 10, true, true, null, null, null, MOCK_QSC)); + assertEquals(ft.rangeQuery(2, 10, true, true, null, null, null, MOCK_QSC), + ft.rangeQuery(1.1, 10, false, true, null, null, null, MOCK_QSC)); + assertEquals(ft.rangeQuery(1, 10, true, true, null, null, null, MOCK_QSC), + ft.rangeQuery(1, 10.1, true, true, null, null, null, MOCK_QSC)); + assertEquals(ft.rangeQuery(1, 10, true, true, null, null, null, MOCK_QSC), + ft.rangeQuery(1, 10.1, true, false, null, null, null, MOCK_QSC)); } public void testIntegerRangeQueryWithDecimalParts() { MappedFieldType ft = new NumberFieldMapper.NumberFieldType(NumberType.INTEGER); ft.setName("field"); ft.setIndexOptions(IndexOptions.DOCS); - assertEquals(ft.rangeQuery(2, 10, true, true, null, null, null, null), - ft.rangeQuery(1.1, 10, true, true, null, null, null, null)); - assertEquals(ft.rangeQuery(2, 10, true, true, null, null, null, null), - ft.rangeQuery(1.1, 10, false, true, null, null, null, null)); - assertEquals(ft.rangeQuery(1, 10, true, true, null, null, null, null), - ft.rangeQuery(1, 10.1, true, true, null, null, null, null)); - assertEquals(ft.rangeQuery(1, 10, true, true, null, null, null, null), - ft.rangeQuery(1, 10.1, true, false, null, null, null, null)); + assertEquals(ft.rangeQuery(2, 10, true, true, null, null, null, MOCK_QSC), + ft.rangeQuery(1.1, 10, true, true, null, null, null, MOCK_QSC)); + assertEquals(ft.rangeQuery(2, 10, true, true, null, null, null, MOCK_QSC), + ft.rangeQuery(1.1, 10, false, true, null, null, null, MOCK_QSC)); + assertEquals(ft.rangeQuery(1, 10, true, true, null, null, null, MOCK_QSC), + ft.rangeQuery(1, 10.1, true, true, null, null, null, MOCK_QSC)); + assertEquals(ft.rangeQuery(1, 10, true, true, null, null, null, MOCK_QSC), + ft.rangeQuery(1, 10.1, true, false, null, null, null, MOCK_QSC)); } public void testLongRangeQueryWithDecimalParts() { MappedFieldType ft = new NumberFieldMapper.NumberFieldType(NumberType.LONG); ft.setName("field"); ft.setIndexOptions(IndexOptions.DOCS); - assertEquals(ft.rangeQuery(2, 10, true, true, null, null, null, null), - ft.rangeQuery(1.1, 10, true, true, null, null, null, null)); - assertEquals(ft.rangeQuery(2, 10, true, true, null, null, null, null), - ft.rangeQuery(1.1, 10, false, true, null, null, null, null)); - assertEquals(ft.rangeQuery(1, 10, true, true, null, null, null, null), - ft.rangeQuery(1, 10.1, true, true, null, null, null, null)); - assertEquals(ft.rangeQuery(1, 10, true, true, null, null, null, null), - ft.rangeQuery(1, 10.1, true, false, null, null, null, null)); + assertEquals(ft.rangeQuery(2, 10, true, true, null, null, null, MOCK_QSC), + ft.rangeQuery(1.1, 10, true, true, null, null, null, MOCK_QSC)); + assertEquals(ft.rangeQuery(2, 10, true, true, null, null, null, MOCK_QSC), + ft.rangeQuery(1.1, 10, false, true, null, null, null, MOCK_QSC)); + assertEquals(ft.rangeQuery(1, 10, true, true, null, null, null, MOCK_QSC), + ft.rangeQuery(1, 10.1, true, true, null, null, null, MOCK_QSC)); + assertEquals(ft.rangeQuery(1, 10, true, true, null, null, null, MOCK_QSC), + ft.rangeQuery(1, 10.1, true, false, null, null, null, MOCK_QSC)); } public void testRangeQuery() { @@ -269,11 +280,11 @@ public class NumberFieldTypeTests extends FieldTypeTestCase { Query expected = new IndexOrDocValuesQuery( LongPoint.newRangeQuery("field", 1, 3), SortedNumericDocValuesField.newSlowRangeQuery("field", 1, 3)); - assertEquals(expected, ft.rangeQuery("1", "3", true, true, null, null, null, null)); + assertEquals(expected, ft.rangeQuery("1", "3", true, true, null, null, null, MOCK_QSC)); ft.setIndexOptions(IndexOptions.NONE); IllegalArgumentException e = expectThrows(IllegalArgumentException.class, - () -> ft.rangeQuery("1", "3", true, true, null, null, null, null)); + () -> ft.rangeQuery("1", "3", true, true, null, null, null, MOCK_QSC)); assertEquals("Cannot search on field [field] since it is not indexed.", e.getMessage()); } @@ -357,6 +368,7 @@ public class NumberFieldTypeTests extends FieldTypeTestCase { } final DirectoryReader reader = DirectoryReader.open(w); w.close(); + IndexSearcher searcher = newSearcher(reader); final int numQueries = 1000; for (int i = 0; i < numQueries; ++i) { @@ -364,8 +376,8 @@ public class NumberFieldTypeTests extends FieldTypeTestCase { float u = (randomFloat() * 2 - 1) * 65504; boolean includeLower = randomBoolean(); boolean includeUpper = randomBoolean(); - Query floatQ = NumberFieldMapper.NumberType.FLOAT.rangeQuery("float", l, u, includeLower, includeUpper, false); - Query halfFloatQ = NumberFieldMapper.NumberType.HALF_FLOAT.rangeQuery("half_float", l, u, includeLower, includeUpper, false); + Query floatQ = NumberType.FLOAT.rangeQuery("float", l, u, includeLower, includeUpper, false, MOCK_QSC); + Query halfFloatQ = NumberType.HALF_FLOAT.rangeQuery("half_float", l, u, includeLower, includeUpper, false, MOCK_QSC); assertEquals(searcher.count(floatQ), searcher.count(halfFloatQ)); } IOUtils.close(reader, dir); @@ -373,14 +385,14 @@ public class NumberFieldTypeTests extends FieldTypeTestCase { public void testNegativeZero() { assertEquals( - NumberType.DOUBLE.rangeQuery("field", null, -0d, true, true, false), - NumberType.DOUBLE.rangeQuery("field", null, +0d, true, false, false)); + NumberType.DOUBLE.rangeQuery("field", null, -0d, true, true, false, MOCK_QSC), + NumberType.DOUBLE.rangeQuery("field", null, +0d, true, false, false, MOCK_QSC)); assertEquals( - NumberType.FLOAT.rangeQuery("field", null, -0f, true, true, false), - NumberType.FLOAT.rangeQuery("field", null, +0f, true, false, false)); + NumberType.FLOAT.rangeQuery("field", null, -0f, true, true, false, MOCK_QSC), + NumberType.FLOAT.rangeQuery("field", null, +0f, true, false, false, MOCK_QSC)); assertEquals( - NumberType.HALF_FLOAT.rangeQuery("field", null, -0f, true, true, false), - NumberType.HALF_FLOAT.rangeQuery("field", null, +0f, true, false, false)); + NumberType.HALF_FLOAT.rangeQuery("field", null, -0f, true, true, false, MOCK_QSC), + NumberType.HALF_FLOAT.rangeQuery("field", null, +0f, true, false, false, MOCK_QSC)); assertFalse(NumberType.DOUBLE.termQuery("field", -0d).equals(NumberType.DOUBLE.termQuery("field", +0d))); assertFalse(NumberType.FLOAT.termQuery("field", -0f).equals(NumberType.FLOAT.termQuery("field", +0f))); @@ -432,8 +444,8 @@ public class NumberFieldTypeTests extends FieldTypeTestCase { Query query = type.rangeQuery("foo", random().nextBoolean() ? null : valueSupplier.get(), random().nextBoolean() ? null : valueSupplier.get(), - randomBoolean(), randomBoolean(), true); - assertThat(query, Matchers.instanceOf(IndexOrDocValuesQuery.class)); + randomBoolean(), randomBoolean(), true, MOCK_QSC); + assertThat(query, instanceOf(IndexOrDocValuesQuery.class)); IndexOrDocValuesQuery indexOrDvQuery = (IndexOrDocValuesQuery) query; assertEquals( searcher.count(indexOrDvQuery.getIndexQuery()), @@ -443,6 +455,75 @@ public class NumberFieldTypeTests extends FieldTypeTestCase { dir.close(); } + public void testIndexSortIntRange() throws Exception { + doTestIndexSortRangeQueries(NumberType.INTEGER, random()::nextInt); + } + + public void testIndexSortLongRange() throws Exception { + doTestIndexSortRangeQueries(NumberType.LONG, random()::nextLong); + } + + public void doTestIndexSortRangeQueries(NumberType type, Supplier valueSupplier) throws IOException { + // Create index settings with an index sort. + Settings settings = Settings.builder() + .put(IndexMetadata.SETTING_VERSION_CREATED, Version.CURRENT) + .put(IndexMetadata.SETTING_NUMBER_OF_SHARDS, 1) + .put(IndexMetadata.SETTING_NUMBER_OF_REPLICAS, 1) + .put("index.sort.field", "field") + .build(); + + IndexMetadata indexMetadata = new IndexMetadata.Builder("index") + .settings(settings) + .build(); + IndexSettings indexSettings = new IndexSettings(indexMetadata, settings); + + // Create an index writer configured with the same index sort. + NumberFieldType fieldType = new NumberFieldType(type); + fieldType.setName("field"); + IndexNumericFieldData fielddata = (IndexNumericFieldData) fieldType.fielddataBuilder("index") + .build(indexSettings, fieldType, null, null, null); + SortField sortField = fielddata.sortField(null, MultiValueMode.MIN, null, randomBoolean()); + + IndexWriterConfig writerConfig = new IndexWriterConfig(); + writerConfig.setIndexSort(new Sort(sortField)); + + Directory dir = newDirectory(); + IndexWriter w = new IndexWriter(dir, writerConfig); + final int numDocs = TestUtil.nextInt(random(), 100, 500); + for (int i = 0; i < numDocs; ++i) { + w.addDocument(type.createFields("field", valueSupplier.get(), true, true, false)); + } + + // Ensure that the optimized index sort query gives the same results as a points query. + DirectoryReader reader = DirectoryReader.open(w); + IndexSearcher searcher = newSearcher(reader); + + QueryShardContext context = new QueryShardContext(0, indexSettings, + BigArrays.NON_RECYCLING_INSTANCE, null, null, null, null, null, xContentRegistry(), writableRegistry(), + null, null, () -> 0L, null, null, () -> true, null); + + final int iters = 10; + for (int iter = 0; iter < iters; ++iter) { + Query query = type.rangeQuery("field", + random().nextBoolean() ? null : valueSupplier.get(), + random().nextBoolean() ? null : valueSupplier.get(), + randomBoolean(), randomBoolean(), true, context); + assertThat(query, instanceOf(IndexSortSortedNumericDocValuesRangeQuery.class)); + + Query fallbackQuery = ((IndexSortSortedNumericDocValuesRangeQuery) query).getFallbackQuery(); + assertThat(fallbackQuery, instanceOf(IndexOrDocValuesQuery.class)); + + IndexOrDocValuesQuery indexOrDvQuery = (IndexOrDocValuesQuery) fallbackQuery; + assertEquals( + searcher.count(query), + searcher.count(indexOrDvQuery.getIndexQuery())); + } + + reader.close(); + w.close(); + dir.close(); + } + public void testParseOutOfRangeValues() throws IOException { final List> inputs = Arrays.asList( OutOfRangeSpec.of(NumberType.BYTE, "128", "out of range for a byte"), diff --git a/server/src/test/java/org/elasticsearch/index/query/QueryShardContextTests.java b/server/src/test/java/org/elasticsearch/index/query/QueryShardContextTests.java index 147af47d413..dc8e6d48a5a 100644 --- a/server/src/test/java/org/elasticsearch/index/query/QueryShardContextTests.java +++ b/server/src/test/java/org/elasticsearch/index/query/QueryShardContextTests.java @@ -134,6 +134,28 @@ public class QueryShardContextTests extends ESTestCase { assertThat(shardContext.getFullyQualifiedIndex().getUUID(), equalTo(indexUuid)); } + public void testIndexSortedOnField() { + Settings settings = Settings.builder() + .put(IndexMetadata.SETTING_VERSION_CREATED, Version.CURRENT) + .put(IndexMetadata.SETTING_NUMBER_OF_SHARDS, 1) + .put(IndexMetadata.SETTING_NUMBER_OF_REPLICAS, 1) + .put("index.sort.field", "sort_field") + .build(); + IndexMetadata indexMetadata = new IndexMetadata.Builder("index") + .settings(settings) + .build(); + + IndexSettings indexSettings = new IndexSettings(indexMetadata, settings); + QueryShardContext context = new QueryShardContext( + 0, indexSettings, BigArrays.NON_RECYCLING_INSTANCE, null, null, + null, null, null, NamedXContentRegistry.EMPTY, new NamedWriteableRegistry(Collections.emptyList()), + null, null, () -> 0L, null, null, () -> true, null); + + assertTrue(context.indexSortedOnField("sort_field")); + assertFalse(context.indexSortedOnField("second_sort_field")); + assertFalse(context.indexSortedOnField("non_sort_field")); + } + public static QueryShardContext createQueryShardContext(String indexUuid, String clusterAlias) { IndexMetadata.Builder indexMetadataBuilder = new IndexMetadata.Builder("index"); indexMetadataBuilder.settings(Settings.builder().put("index.version.created", Version.CURRENT) diff --git a/server/src/test/java/org/elasticsearch/index/search/NestedHelperTests.java b/server/src/test/java/org/elasticsearch/index/search/NestedHelperTests.java index 6cae5d9d179..c3b9519148a 100644 --- a/server/src/test/java/org/elasticsearch/index/search/NestedHelperTests.java +++ b/server/src/test/java/org/elasticsearch/index/search/NestedHelperTests.java @@ -178,28 +178,29 @@ public class NestedHelperTests extends ESSingleNodeTestCase { } public void testRangeQuery() { - Query rangeQuery = mapperService.fieldType("foo2").rangeQuery(2, 5, true, true, null, null, null, null); + QueryShardContext context = createSearchContext(indexService).getQueryShardContext(); + Query rangeQuery = mapperService.fieldType("foo2").rangeQuery(2, 5, true, true, null, null, null, context); assertFalse(new NestedHelper(mapperService).mightMatchNestedDocs(rangeQuery)); assertTrue(new NestedHelper(mapperService).mightMatchNonNestedDocs(rangeQuery, "nested1")); assertTrue(new NestedHelper(mapperService).mightMatchNonNestedDocs(rangeQuery, "nested2")); assertTrue(new NestedHelper(mapperService).mightMatchNonNestedDocs(rangeQuery, "nested3")); assertTrue(new NestedHelper(mapperService).mightMatchNonNestedDocs(rangeQuery, "nested_missing")); - rangeQuery = mapperService.fieldType("nested1.foo2").rangeQuery(2, 5, true, true, null, null, null, null); + rangeQuery = mapperService.fieldType("nested1.foo2").rangeQuery(2, 5, true, true, null, null, null, context); assertTrue(new NestedHelper(mapperService).mightMatchNestedDocs(rangeQuery)); assertFalse(new NestedHelper(mapperService).mightMatchNonNestedDocs(rangeQuery, "nested1")); assertTrue(new NestedHelper(mapperService).mightMatchNonNestedDocs(rangeQuery, "nested2")); assertTrue(new NestedHelper(mapperService).mightMatchNonNestedDocs(rangeQuery, "nested3")); assertTrue(new NestedHelper(mapperService).mightMatchNonNestedDocs(rangeQuery, "nested_missing")); - rangeQuery = mapperService.fieldType("nested2.foo2").rangeQuery(2, 5, true, true, null, null, null, null); + rangeQuery = mapperService.fieldType("nested2.foo2").rangeQuery(2, 5, true, true, null, null, null, context); assertTrue(new NestedHelper(mapperService).mightMatchNestedDocs(rangeQuery)); assertTrue(new NestedHelper(mapperService).mightMatchNonNestedDocs(rangeQuery, "nested1")); assertTrue(new NestedHelper(mapperService).mightMatchNonNestedDocs(rangeQuery, "nested2")); assertTrue(new NestedHelper(mapperService).mightMatchNonNestedDocs(rangeQuery, "nested3")); assertTrue(new NestedHelper(mapperService).mightMatchNonNestedDocs(rangeQuery, "nested_missing")); - rangeQuery = mapperService.fieldType("nested3.foo2").rangeQuery(2, 5, true, true, null, null, null, null); + rangeQuery = mapperService.fieldType("nested3.foo2").rangeQuery(2, 5, true, true, null, null, null, context); assertTrue(new NestedHelper(mapperService).mightMatchNestedDocs(rangeQuery)); assertTrue(new NestedHelper(mapperService).mightMatchNonNestedDocs(rangeQuery, "nested1")); assertTrue(new NestedHelper(mapperService).mightMatchNonNestedDocs(rangeQuery, "nested2"));