Skip to content

Commit 3e095e8

Browse files
authored
Add isFieldMapped method to QueryShardContext (#63322)
As part of #63239 we have moved some usages of QueryShardContext#getMapperService that were looking up a field type through the fieldType method, to use the existing QueryShardContext#fieldMapper. The latter though has additional handling for unmapped fields when the functionality is enabled, and may throw exception if the field is not mapped. This additional behaviour is not desirable and may cause issues in cases where the callers have their own specific handling for situations where the field is not mapped. To address this we introduce an additional isFieldMapped method to QueryShardContext that allows to check if a field is mapped or not. relates to #63239
1 parent 22a7542 commit 3e095e8

File tree

19 files changed

+122
-68
lines changed

19 files changed

+122
-68
lines changed

server/src/main/java/org/elasticsearch/index/fieldvisitor/FieldsVisitor.java

-4
Original file line numberDiff line numberDiff line change
@@ -95,10 +95,6 @@ public final void postProcess(Function<String, MappedFieldType> fieldTypeLookup,
9595
}
9696
for (Map.Entry<String, List<Object>> entry : fields().entrySet()) {
9797
MappedFieldType fieldType = fieldTypeLookup.apply(entry.getKey());
98-
if (fieldType == null) {
99-
throw new IllegalStateException("Field [" + entry.getKey()
100-
+ "] exists in the index but not in mappings");
101-
}
10298
List<Object> fieldValues = entry.getValue();
10399
for (int i = 0; i < fieldValues.size(); i++) {
104100
fieldValues.set(i, fieldType.valueForDisplay(fieldValues.get(i)));

server/src/main/java/org/elasticsearch/index/query/ExistsQueryBuilder.java

+6-10
Original file line numberDiff line numberDiff line change
@@ -194,17 +194,17 @@ private static Query newLegacyExistsQuery(QueryShardContext context, String fiel
194194
}
195195

196196
private static Query newFieldExistsQuery(QueryShardContext context, String field) {
197-
MappedFieldType fieldType = context.fieldMapper(field);
198-
if (fieldType == null) {
197+
if (context.isFieldMapped(field)) {
198+
Query filter = context.fieldMapper(field).existsQuery(context);
199+
return new ConstantScoreQuery(filter);
200+
} else {
199201
// The field does not exist as a leaf but could be an object so
200202
// check for an object mapper
201203
if (context.getObjectMapper(field) != null) {
202204
return newObjectFieldExistsQuery(context, field);
203205
}
204206
return Queries.newMatchNoDocsQuery("User requested \"match_none\" query.");
205207
}
206-
Query filter = fieldType.existsQuery(context);
207-
return new ConstantScoreQuery(filter);
208208
}
209209

210210
private static Query newObjectFieldExistsQuery(QueryShardContext context, String objField) {
@@ -222,10 +222,7 @@ private static Query newObjectFieldExistsQuery(QueryShardContext context, String
222222
* @return return collection of fields if exists else return empty.
223223
*/
224224
private static Collection<String> getMappedField(QueryShardContext context, String fieldPattern) {
225-
final FieldNamesFieldMapper.FieldNamesFieldType fieldNamesFieldType = (FieldNamesFieldMapper.FieldNamesFieldType) context
226-
.fieldMapper(FieldNamesFieldMapper.NAME);
227-
228-
if (fieldNamesFieldType == null) {
225+
if (context.isFieldMapped(FieldNamesFieldMapper.NAME) == false) {
229226
// can only happen when no types exist, so no docs exist either
230227
return Collections.emptySet();
231228
}
@@ -241,8 +238,7 @@ private static Collection<String> getMappedField(QueryShardContext context, Stri
241238

242239
if (fields.size() == 1) {
243240
String field = fields.iterator().next();
244-
MappedFieldType fieldType = context.fieldMapper(field);
245-
if (fieldType == null) {
241+
if (context.isFieldMapped(field) == false) {
246242
// The field does not exist as a leaf but could be an object so
247243
// check for an object mapper
248244
if (context.getObjectMapper(field) == null) {

server/src/main/java/org/elasticsearch/index/query/NestedQueryBuilder.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,7 @@ protected Query doToQuery(QueryShardContext context) throws IOException {
302302

303303
// ToParentBlockJoinQuery requires that the inner query only matches documents
304304
// in its child space
305-
NestedHelper nestedHelper = new NestedHelper(context.getMapperService()::getObjectMapper, context.getMapperService()::fieldType);
305+
NestedHelper nestedHelper = new NestedHelper(context::getObjectMapper, context::isFieldMapped);
306306
if (nestedHelper.mightMatchNonNestedDocs(innerQuery, path)) {
307307
innerQuery = Queries.filtered(innerQuery, nestedObjectMapper.nestedTypeFilter());
308308
}

server/src/main/java/org/elasticsearch/index/query/QueryShardContext.java

+16
Original file line numberDiff line numberDiff line change
@@ -243,10 +243,26 @@ public Set<String> simpleMatchToIndexNames(String pattern) {
243243
return mapperService.simpleMatchToFullName(pattern);
244244
}
245245

246+
/**
247+
* Returns the {@link MappedFieldType} for the provided field name.
248+
* If the field is not mapped, the behaviour depends on the index.query.parse.allow_unmapped_fields setting, which defaults to true.
249+
* In case unmapped fields are allowed, null is returned when the field is not mapped.
250+
* In case unmapped fields are not allowed, either an exception is thrown or the field is automatically mapped as a text field.
251+
* @throws QueryShardException if unmapped fields are not allowed and automatically mapping unmapped fields as text is disabled.
252+
* @see QueryShardContext#setAllowUnmappedFields(boolean)
253+
* @see QueryShardContext#setMapUnmappedFieldAsString(boolean)
254+
*/
246255
public MappedFieldType fieldMapper(String name) {
247256
return failIfFieldMappingNotFound(name, mapperService.fieldType(name));
248257
}
249258

259+
/**
260+
* Returns true if the field identified by the provided name is mapped, false otherwise
261+
*/
262+
public boolean isFieldMapped(String name) {
263+
return mapperService.fieldType(name) != null;
264+
}
265+
250266
public ObjectMapper getObjectMapper(String name) {
251267
return mapperService.getObjectMapper(name);
252268
}

server/src/main/java/org/elasticsearch/index/query/RangeQueryBuilder.java

+4-4
Original file line numberDiff line numberDiff line change
@@ -475,15 +475,15 @@ protected QueryBuilder doRewrite(QueryRewriteContext queryRewriteContext) throws
475475
@Override
476476
protected Query doToQuery(QueryShardContext context) throws IOException {
477477
if (from == null && to == null) {
478-
/**
478+
/*
479479
* Open bounds on both side, we can rewrite to an exists query
480480
* if the {@link FieldNamesFieldMapper} is enabled.
481481
*/
482-
final FieldNamesFieldMapper.FieldNamesFieldType fieldNamesFieldType =
483-
(FieldNamesFieldMapper.FieldNamesFieldType) context.fieldMapper(FieldNamesFieldMapper.NAME);
484-
if (fieldNamesFieldType == null) {
482+
if (context.isFieldMapped(FieldNamesFieldMapper.NAME) == false) {
485483
return new MatchNoDocsQuery("No mappings yet");
486484
}
485+
final FieldNamesFieldMapper.FieldNamesFieldType fieldNamesFieldType =
486+
(FieldNamesFieldMapper.FieldNamesFieldType) context.fieldMapper(FieldNamesFieldMapper.NAME);
487487
// Exists query would fail if the fieldNames field is disabled.
488488
if (fieldNamesFieldType.isEnabled()) {
489489
return ExistsQueryBuilder.newFilter(context, fieldName, false);

server/src/main/java/org/elasticsearch/index/query/functionscore/FieldValueFactorFunctionBuilder.java

+4-6
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
import org.elasticsearch.common.xcontent.XContentBuilder;
2929
import org.elasticsearch.common.xcontent.XContentParser;
3030
import org.elasticsearch.index.fielddata.IndexNumericFieldData;
31-
import org.elasticsearch.index.mapper.MappedFieldType;
3231
import org.elasticsearch.index.query.QueryShardContext;
3332

3433
import java.io.IOException;
@@ -144,14 +143,13 @@ protected int doHashCode() {
144143

145144
@Override
146145
protected ScoreFunction doToFunction(QueryShardContext context) {
147-
MappedFieldType fieldType = context.fieldMapper(field);
148146
IndexNumericFieldData fieldData = null;
149-
if (fieldType == null) {
150-
if(missing == null) {
147+
if (context.isFieldMapped(field)) {
148+
fieldData = context.getForField(context.fieldMapper(field));
149+
} else {
150+
if (missing == null) {
151151
throw new ElasticsearchException("Unable to find a field mapper for field [" + field + "]. No 'missing' value defined.");
152152
}
153-
} else {
154-
fieldData = context.getForField(fieldType);
155153
}
156154
return new FieldValueFactorFunction(field, factor, modifier, missing, fieldData);
157155
}

server/src/main/java/org/elasticsearch/index/query/functionscore/RandomScoreFunctionBuilder.java

+10-11
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
import org.elasticsearch.common.xcontent.XContentBuilder;
2929
import org.elasticsearch.common.xcontent.XContentParser;
3030
import org.elasticsearch.index.mapper.IdFieldMapper;
31-
import org.elasticsearch.index.mapper.MappedFieldType;
3231
import org.elasticsearch.index.query.QueryShardContext;
3332

3433
import java.io.IOException;
@@ -161,15 +160,15 @@ protected ScoreFunction doToFunction(QueryShardContext context) {
161160
// DocID-based random score generation
162161
return new RandomScoreFunction(hash(context.nowInMillis()), salt, null);
163162
} else {
164-
final MappedFieldType fieldType;
165-
if (field != null) {
166-
fieldType = context.fieldMapper(field);
167-
} else {
163+
String fieldName;
164+
if (field == null) {
168165
deprecationLogger.deprecate("seed_requires_field",
169166
"As of version 7.0 Elasticsearch will require that a [field] parameter is provided when a [seed] is set");
170-
fieldType = context.fieldMapper(IdFieldMapper.NAME);
167+
fieldName = IdFieldMapper.NAME;
168+
} else {
169+
fieldName = field;
171170
}
172-
if (fieldType == null) {
171+
if (context.isFieldMapped(fieldName) == false) {
173172
if (context.getMapperService().documentMapper() == null) {
174173
// no mappings: the index is empty anyway
175174
return new RandomScoreFunction(hash(context.nowInMillis()), salt, null);
@@ -178,12 +177,12 @@ protected ScoreFunction doToFunction(QueryShardContext context) {
178177
"] and cannot be used as a source of random numbers.");
179178
}
180179
int seed;
181-
if (this.seed != null) {
182-
seed = this.seed;
183-
} else {
180+
if (this.seed == null) {
184181
seed = hash(context.nowInMillis());
182+
} else {
183+
seed = this.seed;
185184
}
186-
return new RandomScoreFunction(seed, salt, context.getForField(fieldType));
185+
return new RandomScoreFunction(seed, salt, context.getForField(context.fieldMapper(fieldName)));
187186
}
188187
}
189188

server/src/main/java/org/elasticsearch/index/search/NestedHelper.java

+5-6
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@
3232
import org.apache.lucene.search.Query;
3333
import org.apache.lucene.search.TermInSetQuery;
3434
import org.apache.lucene.search.TermQuery;
35-
import org.elasticsearch.index.mapper.MappedFieldType;
3635
import org.elasticsearch.index.mapper.ObjectMapper;
3736

3837
import java.util.function.Function;
@@ -42,11 +41,11 @@
4241
public final class NestedHelper {
4342

4443
private final Function<String, ObjectMapper> objectMapperLookup;
45-
private final Function<String, MappedFieldType> fieldTypeLookup;
44+
private final Function<String, Boolean> isMappedFieldFunction;
4645

47-
public NestedHelper(Function<String, ObjectMapper> objectMapperLookup, Function<String, MappedFieldType> fieldTypeLookup) {
46+
public NestedHelper(Function<String, ObjectMapper> objectMapperLookup, Function<String, Boolean> isMappedFieldFunction) {
4847
this.objectMapperLookup = objectMapperLookup;
49-
this.fieldTypeLookup = fieldTypeLookup;
48+
this.isMappedFieldFunction = isMappedFieldFunction;
5049
}
5150

5251
/** Returns true if the given query might match nested documents. */
@@ -107,7 +106,7 @@ boolean mightMatchNestedDocs(String field) {
107106
// we might add a nested filter when it is nor required.
108107
return true;
109108
}
110-
if (fieldTypeLookup.apply(field) == null) {
109+
if (isMappedFieldFunction.apply(field) == false) {
111110
// field does not exist
112111
return false;
113112
}
@@ -176,7 +175,7 @@ boolean mightMatchNonNestedDocs(String field, String nestedPath) {
176175
// we might add a nested filter when it is nor required.
177176
return true;
178177
}
179-
if (fieldTypeLookup.apply(field) == null) {
178+
if (isMappedFieldFunction.apply(field) == false) {
180179
return false;
181180
}
182181
for (String parent = parentObject(field); parent != null; parent = parentObject(parent)) {

server/src/main/java/org/elasticsearch/index/search/QueryParserHelper.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -125,11 +125,11 @@ static Map<String, Float> resolveMappingField(QueryShardContext context, String
125125
fieldName = fieldName + fieldSuffix;
126126
}
127127

128-
MappedFieldType fieldType = context.fieldMapper(fieldName);
129-
if (fieldType == null) {
128+
if (context.isFieldMapped(fieldName) == false) {
130129
continue;
131130
}
132131

132+
MappedFieldType fieldType = context.fieldMapper(fieldName);
133133
if (acceptMetadataField == false && fieldType.name().startsWith("_")) {
134134
// Ignore metadata fields
135135
continue;

server/src/main/java/org/elasticsearch/index/search/QueryStringQueryParser.java

+3-4
Original file line numberDiff line numberDiff line change
@@ -623,16 +623,15 @@ private Query getPossiblyAnalyzedPrefixQuery(String field, String termStr, Mappe
623623
}
624624

625625
private Query existsQuery(String fieldName) {
626-
final FieldNamesFieldMapper.FieldNamesFieldType fieldNamesFieldType =
627-
(FieldNamesFieldMapper.FieldNamesFieldType) context.fieldMapper(FieldNamesFieldMapper.NAME);
628-
if (fieldNamesFieldType == null) {
626+
if (context.isFieldMapped(FieldNamesFieldMapper.NAME) == false) {
629627
return new MatchNoDocsQuery("No mappings yet");
630628
}
629+
final FieldNamesFieldMapper.FieldNamesFieldType fieldNamesFieldType =
630+
(FieldNamesFieldMapper.FieldNamesFieldType) context.fieldMapper(FieldNamesFieldMapper.NAME);
631631
if (fieldNamesFieldType.isEnabled() == false) {
632632
// The field_names_field is disabled so we switch to a wildcard query that matches all terms
633633
return new WildcardQuery(new Term(fieldName, "*"));
634634
}
635-
636635
return ExistsQueryBuilder.newFilter(context, fieldName, false);
637636
}
638637

server/src/main/java/org/elasticsearch/search/DefaultSearchContext.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,7 @@ public Query buildFilteredQuery(Query query) {
278278
filters.add(typeFilter);
279279
}
280280

281-
NestedHelper nestedHelper = new NestedHelper(mapperService()::getObjectMapper, mapperService()::fieldType);
281+
NestedHelper nestedHelper = new NestedHelper(mapperService()::getObjectMapper, field -> mapperService().fieldType(field) != null);
282282
if (mapperService().hasNested()
283283
&& nestedHelper.mightMatchNestedDocs(query)
284284
&& (aliasFilter == null || nestedHelper.mightMatchNestedDocs(aliasFilter))) {

server/src/main/java/org/elasticsearch/search/suggest/completion/CompletionSuggestionBuilder.java

+9-11
Original file line numberDiff line numberDiff line change
@@ -298,19 +298,17 @@ public SuggestionContext build(QueryShardContext context) throws IOException {
298298
suggestionContext.setShardSize(shardSize);
299299
}
300300
MappedFieldType mappedFieldType = context.fieldMapper(suggestionContext.getField());
301-
if (mappedFieldType == null || mappedFieldType instanceof CompletionFieldMapper.CompletionFieldType == false) {
301+
if (mappedFieldType instanceof CompletionFieldMapper.CompletionFieldType == false) {
302302
throw new IllegalArgumentException("Field [" + suggestionContext.getField() + "] is not a completion suggest field");
303303
}
304-
if (mappedFieldType instanceof CompletionFieldMapper.CompletionFieldType) {
305-
CompletionFieldMapper.CompletionFieldType type = (CompletionFieldMapper.CompletionFieldType) mappedFieldType;
306-
suggestionContext.setFieldType(type);
307-
if (type.hasContextMappings() && contextBytes != null) {
308-
Map<String, List<ContextMapping.InternalQueryContext>> queryContexts = parseContextBytes(contextBytes,
309-
context.getXContentRegistry(), type.getContextMappings());
310-
suggestionContext.setQueryContexts(queryContexts);
311-
} else if (contextBytes != null) {
312-
throw new IllegalArgumentException("suggester [" + type.name() + "] doesn't expect any context");
313-
}
304+
CompletionFieldMapper.CompletionFieldType type = (CompletionFieldMapper.CompletionFieldType) mappedFieldType;
305+
suggestionContext.setFieldType(type);
306+
if (type.hasContextMappings() && contextBytes != null) {
307+
Map<String, List<ContextMapping.InternalQueryContext>> queryContexts = parseContextBytes(contextBytes,
308+
context.getXContentRegistry(), type.getContextMappings());
309+
suggestionContext.setQueryContexts(queryContexts);
310+
} else if (contextBytes != null) {
311+
throw new IllegalArgumentException("suggester [" + type.name() + "] doesn't expect any context");
314312
}
315313
assert suggestionContext.getFieldType() != null : "no completion field type set";
316314
return suggestionContext;

server/src/test/java/org/elasticsearch/index/query/ExistsQueryBuilderTests.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ protected void doAssertLuceneQuery(ExistsQueryBuilder queryBuilder, Query query,
6262
String fieldPattern = queryBuilder.fieldName();
6363
Collection<String> fields = context.simpleMatchToIndexNames(fieldPattern);
6464
Collection<String> mappedFields = fields.stream().filter((field) -> context.getObjectMapper(field) != null
65-
|| context.fieldMapper(field) != null).collect(Collectors.toList());
65+
|| context.isFieldMapped(field)).collect(Collectors.toList());
6666
if (mappedFields.size() == 0) {
6767
assertThat(query, instanceOf(MatchNoDocsQuery.class));
6868
return;
@@ -127,7 +127,7 @@ protected void doAssertLuceneQuery(ExistsQueryBuilder queryBuilder, Query query,
127127
}
128128

129129
@Override
130-
public void testMustRewrite() throws IOException {
130+
public void testMustRewrite() {
131131
QueryShardContext context = createShardContext();
132132
context.setAllowUnmappedFields(true);
133133
ExistsQueryBuilder queryBuilder = new ExistsQueryBuilder("foo");

server/src/test/java/org/elasticsearch/index/query/functionscore/ScoreFunctionBuilderTests.java

+22-3
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
import org.elasticsearch.cluster.metadata.IndexMetadata;
2424
import org.elasticsearch.common.settings.Settings;
2525
import org.elasticsearch.index.IndexSettings;
26+
import org.elasticsearch.index.mapper.IdFieldMapper;
27+
import org.elasticsearch.index.mapper.KeywordFieldMapper;
2628
import org.elasticsearch.index.mapper.MappedFieldType;
2729
import org.elasticsearch.index.mapper.NumberFieldMapper;
2830
import org.elasticsearch.index.mapper.NumberFieldMapper.NumberType;
@@ -49,7 +51,7 @@ public void testIllegalArguments() {
4951
expectThrows(IllegalArgumentException.class, () -> new ExponentialDecayFunctionBuilder("", "", null, "", randomDouble()));
5052
}
5153

52-
public void testRandomScoreFunctionWithSeed() throws Exception {
54+
public void testRandomScoreFunctionWithSeedNoField() throws Exception {
5355
RandomScoreFunctionBuilder builder = new RandomScoreFunctionBuilder();
5456
builder.seed(42);
5557
QueryShardContext context = Mockito.mock(QueryShardContext.class);
@@ -59,9 +61,26 @@ public void testRandomScoreFunctionWithSeed() throws Exception {
5961
Mockito.when(context.index()).thenReturn(settings.getIndex());
6062
Mockito.when(context.getShardId()).thenReturn(0);
6163
Mockito.when(context.getIndexSettings()).thenReturn(settings);
62-
MappedFieldType ft = new NumberFieldMapper.NumberFieldType("foo", NumberType.LONG);
63-
Mockito.when(context.fieldMapper(Mockito.anyString())).thenReturn(ft);
64+
Mockito.when(context.fieldMapper(IdFieldMapper.NAME)).thenReturn(new KeywordFieldMapper.KeywordFieldType(IdFieldMapper.NAME));
65+
Mockito.when(context.isFieldMapped(IdFieldMapper.NAME)).thenReturn(true);
6466
builder.toFunction(context);
6567
assertWarnings("As of version 7.0 Elasticsearch will require that a [field] parameter is provided when a [seed] is set");
6668
}
69+
70+
public void testRandomScoreFunctionWithSeed() throws Exception {
71+
RandomScoreFunctionBuilder builder = new RandomScoreFunctionBuilder();
72+
builder.setField("foo");
73+
builder.seed(42);
74+
QueryShardContext context = Mockito.mock(QueryShardContext.class);
75+
Settings indexSettings = Settings.builder().put(IndexMetadata.SETTING_VERSION_CREATED, Version.CURRENT)
76+
.put(IndexMetadata.SETTING_NUMBER_OF_SHARDS, 1).put(IndexMetadata.SETTING_NUMBER_OF_REPLICAS, 1).build();
77+
IndexSettings settings = new IndexSettings(IndexMetadata.builder("index").settings(indexSettings).build(), Settings.EMPTY);
78+
Mockito.when(context.index()).thenReturn(settings.getIndex());
79+
Mockito.when(context.getShardId()).thenReturn(0);
80+
Mockito.when(context.getIndexSettings()).thenReturn(settings);
81+
MappedFieldType ft = new NumberFieldMapper.NumberFieldType("foo", NumberType.LONG);
82+
Mockito.when(context.fieldMapper("foo")).thenReturn(ft);
83+
Mockito.when(context.isFieldMapped("foo")).thenReturn(true);
84+
builder.toFunction(context);
85+
}
6786
}

server/src/test/java/org/elasticsearch/index/search/NestedHelperTests.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ public void setUp() throws Exception {
102102
}
103103

104104
private static NestedHelper buildNestedHelper(MapperService mapperService) {
105-
return new NestedHelper(mapperService::getObjectMapper, mapperService::fieldType);
105+
return new NestedHelper(mapperService::getObjectMapper, field -> mapperService.fieldType(field) != null);
106106
}
107107

108108
public void testMatchAll() {

0 commit comments

Comments
 (0)