Skip to content

Commit 864711b

Browse files
author
Christoph Büscher
authored
Fix range queries on _type field for singe type indices (#31756) (#32161)
With the introduction of single types in 6.x, the `_type` field is no longer indexed, which leads to certain queries that were working before throw errors now. One such query is the `range` query, that, if performed on a single typer index, currently throws an IAE since the field is not indexed. This change adds special treatment for this case in the TypeFieldMapper, comparing the range queries lower and upper bound to the one existing type and either returns a MatchAllDocs or a MatchNoDocs query. Relates to #31632 Closes #31476
1 parent 373cec0 commit 864711b

File tree

3 files changed

+123
-13
lines changed

3 files changed

+123
-13
lines changed

server/src/main/java/org/elasticsearch/index/mapper/TypeFieldMapper.java

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,10 @@
3434
import org.apache.lucene.search.Query;
3535
import org.apache.lucene.search.TermInSetQuery;
3636
import org.apache.lucene.search.TermQuery;
37+
import org.apache.lucene.search.TermRangeQuery;
3738
import org.apache.lucene.util.BytesRef;
39+
import org.elasticsearch.common.logging.DeprecationLogger;
40+
import org.elasticsearch.common.logging.ESLoggerFactory;
3841
import org.elasticsearch.common.lucene.Lucene;
3942
import org.elasticsearch.common.lucene.search.Queries;
4043
import org.elasticsearch.common.xcontent.XContentBuilder;
@@ -91,6 +94,8 @@ public MetadataFieldMapper getDefault(MappedFieldType fieldType, ParserContext c
9194

9295
static final class TypeFieldType extends StringFieldType {
9396

97+
private static final DeprecationLogger DEPRECATION_LOGGER = new DeprecationLogger(ESLoggerFactory.getLogger(TypeFieldType.class));
98+
9499
TypeFieldType() {
95100
}
96101

@@ -174,6 +179,38 @@ public Query termsQuery(List<?> values, QueryShardContext context) {
174179
}
175180
}
176181

182+
@Override
183+
public Query rangeQuery(Object lowerTerm, Object upperTerm, boolean includeLower, boolean includeUpper, QueryShardContext context) {
184+
if (context.getIndexSettings().isSingleType() == false) {
185+
return new TermRangeQuery(name(), lowerTerm == null ? null : indexedValueForSearch(lowerTerm),
186+
upperTerm == null ? null : indexedValueForSearch(upperTerm), includeLower, includeUpper);
187+
} else {
188+
// this means the index has a single type and the type field is implicit
189+
DEPRECATION_LOGGER.deprecatedAndMaybeLog("range_single_type",
190+
"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.");
191+
Collection<String> types = context.getMapperService().types();
192+
String type = types.iterator().hasNext() ? types.iterator().next() : null;
193+
if (type != null) {
194+
Query result = new MatchAllDocsQuery();
195+
BytesRef typeBytes = new BytesRef(type);
196+
if (lowerTerm != null) {
197+
int comp = indexedValueForSearch(lowerTerm).compareTo(typeBytes);
198+
if (comp > 0 || (comp == 0 && includeLower == false)) {
199+
result = new MatchNoDocsQuery("[_type] was lexicographically smaller than lower bound of range");
200+
}
201+
}
202+
if (upperTerm != null) {
203+
int comp = indexedValueForSearch(upperTerm).compareTo(typeBytes);
204+
if (comp < 0 || (comp == 0 && includeUpper == false)) {
205+
result = new MatchNoDocsQuery("[_type] was lexicographically greater than upper bound of range");
206+
}
207+
}
208+
return result;
209+
} else {
210+
return new MatchNoDocsQuery();
211+
}
212+
}
213+
}
177214
}
178215

179216
/**

server/src/test/java/org/elasticsearch/index/mapper/TypeFieldTypeTests.java

Lines changed: 50 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,15 @@
3434
import org.apache.lucene.search.PhraseQuery;
3535
import org.apache.lucene.search.Query;
3636
import org.apache.lucene.search.TermQuery;
37+
import org.apache.lucene.search.TermRangeQuery;
3738
import org.apache.lucene.store.Directory;
3839
import org.apache.lucene.util.BytesRef;
39-
import org.elasticsearch.core.internal.io.IOUtils;
4040
import org.elasticsearch.Version;
4141
import org.elasticsearch.cluster.metadata.IndexMetaData;
4242
import org.elasticsearch.common.UUIDs;
4343
import org.elasticsearch.common.lucene.search.Queries;
4444
import org.elasticsearch.common.settings.Settings;
45+
import org.elasticsearch.core.internal.io.IOUtils;
4546
import org.elasticsearch.index.IndexSettings;
4647
import org.elasticsearch.index.query.QueryShardContext;
4748
import org.elasticsearch.test.VersionUtils;
@@ -51,15 +52,17 @@
5152
import java.util.Collections;
5253
import java.util.Set;
5354

55+
import static org.hamcrest.Matchers.instanceOf;
56+
5457
public class TypeFieldTypeTests extends FieldTypeTestCase {
5558
@Override
5659
protected MappedFieldType createDefaultFieldType() {
5760
return new TypeFieldMapper.TypeFieldType();
5861
}
5962

60-
public void testTermsQueryWhenTypesAreDisabled() throws Exception {
63+
private QueryShardContext createMockContext(Version versionFrom, Version versionTo) {
6164
QueryShardContext context = Mockito.mock(QueryShardContext.class);
62-
Version indexVersionCreated = VersionUtils.randomVersionBetween(random(), Version.V_6_0_0, Version.CURRENT);
65+
Version indexVersionCreated = VersionUtils.randomVersionBetween(random(), versionFrom, versionTo);
6366
Settings indexSettings = Settings.builder()
6467
.put(IndexMetaData.SETTING_VERSION_CREATED, indexVersionCreated)
6568
.put(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, 0)
@@ -70,6 +73,12 @@ public void testTermsQueryWhenTypesAreDisabled() throws Exception {
7073
Mockito.when(context.getIndexSettings()).thenReturn(mockSettings);
7174
Mockito.when(context.indexVersionCreated()).thenReturn(indexVersionCreated);
7275

76+
return context;
77+
}
78+
79+
public void testTermsQueryWhenTypesAreDisabled() throws Exception {
80+
QueryShardContext context = createMockContext(Version.V_6_0_0, Version.CURRENT);
81+
7382
MapperService mapperService = Mockito.mock(MapperService.class);
7483
Set<String> types = Collections.emptySet();
7584
Mockito.when(mapperService.types()).thenReturn(types);
@@ -100,16 +109,7 @@ public void testTermsQueryWhenTypesAreEnabled() throws Exception {
100109
IndexWriter w = new IndexWriter(dir, newIndexWriterConfig());
101110
IndexReader reader = openReaderWithNewType("my_type", w);
102111

103-
QueryShardContext context = Mockito.mock(QueryShardContext.class);
104-
Settings indexSettings = Settings.builder()
105-
.put(IndexMetaData.SETTING_VERSION_CREATED, Version.V_5_6_0) // to allow for multiple types
106-
.put(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, 0)
107-
.put(IndexMetaData.SETTING_NUMBER_OF_SHARDS, 1)
108-
.put(IndexMetaData.SETTING_INDEX_UUID, UUIDs.randomBase64UUID())
109-
.build();
110-
IndexMetaData indexMetaData = IndexMetaData.builder(IndexMetaData.INDEX_UUID_NA_VALUE).settings(indexSettings).build();
111-
IndexSettings mockSettings = new IndexSettings(indexMetaData, Settings.EMPTY);
112-
Mockito.when(context.getIndexSettings()).thenReturn(mockSettings);
112+
QueryShardContext context = createMockContext(Version.V_5_6_0, Version.V_5_6_0); // to allow for multiple types
113113

114114
TypeFieldMapper.TypeFieldType ft = new TypeFieldMapper.TypeFieldType();
115115
ft.setName(TypeFieldMapper.NAME);
@@ -166,6 +166,43 @@ public void testTermsQueryWhenTypesAreEnabled() throws Exception {
166166
IOUtils.close(reader, w, dir);
167167
}
168168

169+
public void testRangeWhenTypesAreDisabled() throws Exception {
170+
QueryShardContext context = createMockContext(Version.V_6_0_0, Version.CURRENT);
171+
172+
MapperService mapperService = Mockito.mock(MapperService.class);
173+
Set<String> types = Collections.emptySet();
174+
Mockito.when(mapperService.types()).thenReturn(types);
175+
Mockito.when(context.getMapperService()).thenReturn(mapperService);
176+
177+
TypeFieldMapper.TypeFieldType ft = new TypeFieldMapper.TypeFieldType();
178+
ft.setName(TypeFieldMapper.NAME);
179+
Query query = ft.rangeQuery("a_type", "z_type", randomBoolean(), randomBoolean(), context);
180+
assertEquals(new MatchNoDocsQuery(), query);
181+
182+
types = Collections.singleton("my_type");
183+
Mockito.when(mapperService.types()).thenReturn(types);
184+
query = ft.rangeQuery("a_type", "z_type", randomBoolean(), randomBoolean(), context);
185+
assertEquals(new MatchAllDocsQuery(), query);
186+
187+
query = ft.rangeQuery("n_type", "z_type", randomBoolean(), randomBoolean(), context);
188+
assertEquals(new MatchNoDocsQuery(), query);
189+
190+
query = ft.rangeQuery("a_type", "l_type", randomBoolean(), randomBoolean(), context);
191+
assertEquals(new MatchNoDocsQuery(), query);
192+
assertWarnings("Running [range] query on [_type] field for an index with a single type. As types are deprecated, this "
193+
+ "functionality will be removed in future releases.");
194+
}
195+
196+
public void testRangeWhenTypesEnabled() throws Exception {
197+
TypeFieldMapper.TypeFieldType ft = new TypeFieldMapper.TypeFieldType();
198+
ft.setName(TypeFieldMapper.NAME);
199+
String lowerTerm = randomBoolean() ? "a_type" : null;
200+
String upperTerm = randomBoolean() ? "z_type" : null;
201+
QueryShardContext context = createMockContext(Version.V_5_6_0, Version.V_5_6_0); // to allow for multiple types
202+
Query query = ft.rangeQuery(lowerTerm, upperTerm, randomBoolean(), randomBoolean(), context);
203+
assertThat(query, instanceOf(TermRangeQuery.class));
204+
}
205+
169206
static DirectoryReader openReaderWithNewType(String type, IndexWriter writer) throws IOException {
170207
Document doc = new Document();
171208
StringField typeField = new StringField(TypeFieldMapper.NAME, type, Store.NO);

server/src/test/java/org/elasticsearch/search/query/SearchQueryIT.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1858,4 +1858,40 @@ public void testRangeQueryRangeFields_24744() throws Exception {
18581858
SearchResponse searchResponse = client().prepareSearch("test").setQuery(range).get();
18591859
assertHitCount(searchResponse, 1);
18601860
}
1861+
1862+
public void testRangeQueryTypeField_31476() throws Exception {
1863+
assertAcked(prepareCreate("test").addMapping("foo", "field", "type=keyword"));
1864+
1865+
client().prepareIndex("test", "foo", "1").setSource("field", "value").get();
1866+
refresh();
1867+
1868+
RangeQueryBuilder range = new RangeQueryBuilder("_type").from("ape").to("zebra");
1869+
SearchResponse searchResponse = client().prepareSearch("test").setQuery(range).get();
1870+
assertHitCount(searchResponse, 1);
1871+
1872+
range = new RangeQueryBuilder("_type").from("monkey").to("zebra");
1873+
searchResponse = client().prepareSearch("test").setQuery(range).get();
1874+
assertHitCount(searchResponse, 0);
1875+
1876+
range = new RangeQueryBuilder("_type").from("ape").to("donkey");
1877+
searchResponse = client().prepareSearch("test").setQuery(range).get();
1878+
assertHitCount(searchResponse, 0);
1879+
1880+
range = new RangeQueryBuilder("_type").from("ape").to("foo").includeUpper(false);
1881+
searchResponse = client().prepareSearch("test").setQuery(range).get();
1882+
assertHitCount(searchResponse, 0);
1883+
1884+
range = new RangeQueryBuilder("_type").from("ape").to("foo").includeUpper(true);
1885+
searchResponse = client().prepareSearch("test").setQuery(range).get();
1886+
assertHitCount(searchResponse, 1);
1887+
1888+
range = new RangeQueryBuilder("_type").from("foo").to("zebra").includeLower(false);
1889+
searchResponse = client().prepareSearch("test").setQuery(range).get();
1890+
assertHitCount(searchResponse, 0);
1891+
1892+
range = new RangeQueryBuilder("_type").from("foo").to("zebra").includeLower(true);
1893+
searchResponse = client().prepareSearch("test").setQuery(range).get();
1894+
assertHitCount(searchResponse, 1);
1895+
}
1896+
18611897
}

0 commit comments

Comments
 (0)