diff --git a/server/src/main/java/org/elasticsearch/index/mapper/TypeFieldMapper.java b/server/src/main/java/org/elasticsearch/index/mapper/TypeFieldMapper.java index 4160bdc38cc9d..847cc30969826 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/TypeFieldMapper.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/TypeFieldMapper.java @@ -34,7 +34,10 @@ import org.apache.lucene.search.Query; import org.apache.lucene.search.TermInSetQuery; import org.apache.lucene.search.TermQuery; +import org.apache.lucene.search.TermRangeQuery; import org.apache.lucene.util.BytesRef; +import org.elasticsearch.common.logging.DeprecationLogger; +import org.elasticsearch.common.logging.ESLoggerFactory; import org.elasticsearch.common.lucene.Lucene; import org.elasticsearch.common.lucene.search.Queries; import org.elasticsearch.common.xcontent.XContentBuilder; @@ -91,6 +94,8 @@ public MetadataFieldMapper getDefault(MappedFieldType fieldType, ParserContext c static final class TypeFieldType extends StringFieldType { + private static final DeprecationLogger DEPRECATION_LOGGER = new DeprecationLogger(ESLoggerFactory.getLogger(TypeFieldType.class)); + TypeFieldType() { } @@ -174,6 +179,38 @@ public Query termsQuery(List values, QueryShardContext context) { } } + @Override + public Query rangeQuery(Object lowerTerm, Object upperTerm, boolean includeLower, boolean includeUpper, QueryShardContext context) { + if (context.getIndexSettings().isSingleType() == false) { + return new TermRangeQuery(name(), lowerTerm == null ? null : indexedValueForSearch(lowerTerm), + upperTerm == null ? null : indexedValueForSearch(upperTerm), includeLower, includeUpper); + } else { + // this means the index has a single type and the type field is implicit + DEPRECATION_LOGGER.deprecatedAndMaybeLog("range_single_type", + "Running [range] query on [_type] field for an index with a single type. As types are deprecated, this functionality will be removed in future releases."); + Collection types = context.getMapperService().types(); + String type = types.iterator().hasNext() ? types.iterator().next() : null; + if (type != null) { + Query result = new MatchAllDocsQuery(); + BytesRef typeBytes = new BytesRef(type); + if (lowerTerm != null) { + int comp = indexedValueForSearch(lowerTerm).compareTo(typeBytes); + if (comp > 0 || (comp == 0 && includeLower == false)) { + result = new MatchNoDocsQuery("[_type] was lexicographically smaller than lower bound of range"); + } + } + if (upperTerm != null) { + int comp = indexedValueForSearch(upperTerm).compareTo(typeBytes); + if (comp < 0 || (comp == 0 && includeUpper == false)) { + result = new MatchNoDocsQuery("[_type] was lexicographically greater than upper bound of range"); + } + } + return result; + } else { + return new MatchNoDocsQuery(); + } + } + } } /** diff --git a/server/src/test/java/org/elasticsearch/index/mapper/TypeFieldTypeTests.java b/server/src/test/java/org/elasticsearch/index/mapper/TypeFieldTypeTests.java index 1fdfb52b47f67..d9f68e9dce94b 100644 --- a/server/src/test/java/org/elasticsearch/index/mapper/TypeFieldTypeTests.java +++ b/server/src/test/java/org/elasticsearch/index/mapper/TypeFieldTypeTests.java @@ -34,14 +34,15 @@ import org.apache.lucene.search.PhraseQuery; import org.apache.lucene.search.Query; import org.apache.lucene.search.TermQuery; +import org.apache.lucene.search.TermRangeQuery; import org.apache.lucene.store.Directory; import org.apache.lucene.util.BytesRef; -import org.elasticsearch.core.internal.io.IOUtils; import org.elasticsearch.Version; import org.elasticsearch.cluster.metadata.IndexMetaData; import org.elasticsearch.common.UUIDs; import org.elasticsearch.common.lucene.search.Queries; import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.core.internal.io.IOUtils; import org.elasticsearch.index.IndexSettings; import org.elasticsearch.index.query.QueryShardContext; import org.elasticsearch.test.VersionUtils; @@ -51,15 +52,17 @@ import java.util.Collections; import java.util.Set; +import static org.hamcrest.Matchers.instanceOf; + public class TypeFieldTypeTests extends FieldTypeTestCase { @Override protected MappedFieldType createDefaultFieldType() { return new TypeFieldMapper.TypeFieldType(); } - public void testTermsQueryWhenTypesAreDisabled() throws Exception { + private QueryShardContext createMockContext(Version versionFrom, Version versionTo) { QueryShardContext context = Mockito.mock(QueryShardContext.class); - Version indexVersionCreated = VersionUtils.randomVersionBetween(random(), Version.V_6_0_0, Version.CURRENT); + Version indexVersionCreated = VersionUtils.randomVersionBetween(random(), versionFrom, versionTo); Settings indexSettings = Settings.builder() .put(IndexMetaData.SETTING_VERSION_CREATED, indexVersionCreated) .put(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, 0) @@ -70,6 +73,12 @@ public void testTermsQueryWhenTypesAreDisabled() throws Exception { Mockito.when(context.getIndexSettings()).thenReturn(mockSettings); Mockito.when(context.indexVersionCreated()).thenReturn(indexVersionCreated); + return context; + } + + public void testTermsQueryWhenTypesAreDisabled() throws Exception { + QueryShardContext context = createMockContext(Version.V_6_0_0, Version.CURRENT); + MapperService mapperService = Mockito.mock(MapperService.class); Set types = Collections.emptySet(); Mockito.when(mapperService.types()).thenReturn(types); @@ -100,16 +109,7 @@ public void testTermsQueryWhenTypesAreEnabled() throws Exception { IndexWriter w = new IndexWriter(dir, newIndexWriterConfig()); IndexReader reader = openReaderWithNewType("my_type", w); - QueryShardContext context = Mockito.mock(QueryShardContext.class); - Settings indexSettings = Settings.builder() - .put(IndexMetaData.SETTING_VERSION_CREATED, Version.V_5_6_0) // to allow for multiple types - .put(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, 0) - .put(IndexMetaData.SETTING_NUMBER_OF_SHARDS, 1) - .put(IndexMetaData.SETTING_INDEX_UUID, UUIDs.randomBase64UUID()) - .build(); - IndexMetaData indexMetaData = IndexMetaData.builder(IndexMetaData.INDEX_UUID_NA_VALUE).settings(indexSettings).build(); - IndexSettings mockSettings = new IndexSettings(indexMetaData, Settings.EMPTY); - Mockito.when(context.getIndexSettings()).thenReturn(mockSettings); + QueryShardContext context = createMockContext(Version.V_5_6_0, Version.V_5_6_0); // to allow for multiple types TypeFieldMapper.TypeFieldType ft = new TypeFieldMapper.TypeFieldType(); ft.setName(TypeFieldMapper.NAME); @@ -166,6 +166,43 @@ public void testTermsQueryWhenTypesAreEnabled() throws Exception { IOUtils.close(reader, w, dir); } + public void testRangeWhenTypesAreDisabled() throws Exception { + QueryShardContext context = createMockContext(Version.V_6_0_0, Version.CURRENT); + + MapperService mapperService = Mockito.mock(MapperService.class); + Set types = Collections.emptySet(); + Mockito.when(mapperService.types()).thenReturn(types); + Mockito.when(context.getMapperService()).thenReturn(mapperService); + + TypeFieldMapper.TypeFieldType ft = new TypeFieldMapper.TypeFieldType(); + ft.setName(TypeFieldMapper.NAME); + Query query = ft.rangeQuery("a_type", "z_type", randomBoolean(), randomBoolean(), context); + assertEquals(new MatchNoDocsQuery(), query); + + types = Collections.singleton("my_type"); + Mockito.when(mapperService.types()).thenReturn(types); + query = ft.rangeQuery("a_type", "z_type", randomBoolean(), randomBoolean(), context); + assertEquals(new MatchAllDocsQuery(), query); + + query = ft.rangeQuery("n_type", "z_type", randomBoolean(), randomBoolean(), context); + assertEquals(new MatchNoDocsQuery(), query); + + query = ft.rangeQuery("a_type", "l_type", randomBoolean(), randomBoolean(), context); + assertEquals(new MatchNoDocsQuery(), query); + assertWarnings("Running [range] query on [_type] field for an index with a single type. As types are deprecated, this " + + "functionality will be removed in future releases."); + } + + public void testRangeWhenTypesEnabled() throws Exception { + TypeFieldMapper.TypeFieldType ft = new TypeFieldMapper.TypeFieldType(); + ft.setName(TypeFieldMapper.NAME); + String lowerTerm = randomBoolean() ? "a_type" : null; + String upperTerm = randomBoolean() ? "z_type" : null; + QueryShardContext context = createMockContext(Version.V_5_6_0, Version.V_5_6_0); // to allow for multiple types + Query query = ft.rangeQuery(lowerTerm, upperTerm, randomBoolean(), randomBoolean(), context); + assertThat(query, instanceOf(TermRangeQuery.class)); + } + static DirectoryReader openReaderWithNewType(String type, IndexWriter writer) throws IOException { Document doc = new Document(); StringField typeField = new StringField(TypeFieldMapper.NAME, type, Store.NO); diff --git a/server/src/test/java/org/elasticsearch/search/query/SearchQueryIT.java b/server/src/test/java/org/elasticsearch/search/query/SearchQueryIT.java index 367b97eb4da08..47355124d8a02 100644 --- a/server/src/test/java/org/elasticsearch/search/query/SearchQueryIT.java +++ b/server/src/test/java/org/elasticsearch/search/query/SearchQueryIT.java @@ -1858,4 +1858,40 @@ public void testRangeQueryRangeFields_24744() throws Exception { SearchResponse searchResponse = client().prepareSearch("test").setQuery(range).get(); assertHitCount(searchResponse, 1); } + + public void testRangeQueryTypeField_31476() throws Exception { + assertAcked(prepareCreate("test").addMapping("foo", "field", "type=keyword")); + + client().prepareIndex("test", "foo", "1").setSource("field", "value").get(); + refresh(); + + RangeQueryBuilder range = new RangeQueryBuilder("_type").from("ape").to("zebra"); + SearchResponse searchResponse = client().prepareSearch("test").setQuery(range).get(); + assertHitCount(searchResponse, 1); + + range = new RangeQueryBuilder("_type").from("monkey").to("zebra"); + searchResponse = client().prepareSearch("test").setQuery(range).get(); + assertHitCount(searchResponse, 0); + + range = new RangeQueryBuilder("_type").from("ape").to("donkey"); + searchResponse = client().prepareSearch("test").setQuery(range).get(); + assertHitCount(searchResponse, 0); + + range = new RangeQueryBuilder("_type").from("ape").to("foo").includeUpper(false); + searchResponse = client().prepareSearch("test").setQuery(range).get(); + assertHitCount(searchResponse, 0); + + range = new RangeQueryBuilder("_type").from("ape").to("foo").includeUpper(true); + searchResponse = client().prepareSearch("test").setQuery(range).get(); + assertHitCount(searchResponse, 1); + + range = new RangeQueryBuilder("_type").from("foo").to("zebra").includeLower(false); + searchResponse = client().prepareSearch("test").setQuery(range).get(); + assertHitCount(searchResponse, 0); + + range = new RangeQueryBuilder("_type").from("foo").to("zebra").includeLower(true); + searchResponse = client().prepareSearch("test").setQuery(range).get(); + assertHitCount(searchResponse, 1); + } + }