Skip to content

Commit cc2a9e0

Browse files
imotovjasontedor
authored andcommitted
Make Geo Context Mapping Parsing More Strict (#32862)
Currently, if geo context is represented by something other than geo_point or an object with lat and lon fields, the parsing of it as a geo context can result in ignoring the context altogether, returning confusing errors such as number_format_exception or trying to parse the number specifying as long-encoded hash code. It would also fail if the geo_point was stored. This commit makes the deprecates creation of geo contexts without correct path fields. And it fixes the issue when geo_point is stored. This is a 6.x version of #32821.
1 parent e040495 commit cc2a9e0

File tree

7 files changed

+149
-9
lines changed

7 files changed

+149
-9
lines changed

docs/reference/migration/migrate_6_0/search.asciidoc

+6-2
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
* The `mlt` query (a synonym for the `more_like_this` query) has been removed.
1818

1919
* The deprecated `like_text`, `ids` and `docs` parameters (all synonyms for `like`) of the `more_like_this` query have
20-
been removed. Also the deprecated `min_word_len` (a synonym for `min_word_length`) and `max_word_len`
20+
been removed. Also the deprecated `min_word_len` (a synonym for `min_word_length`) and `max_word_len`
2121
(a synonym for `max_word_length`) have been removed.
2222

2323
* The `fuzzy_match` and `match_fuzzy` query (synonyma for the `match` query) have been removed
@@ -215,6 +215,11 @@ The ability to query and index context enabled suggestions without contexts
215215
has been deprecated. Context enabled suggestion queries without contexts have
216216
to visit every suggestion, which degrades the search performance considerably.
217217

218+
For geo context the value of the `path` parameter is now validated against the mapping,
219+
and if `path` points to a non `geo_point` field or the field doesn't exist a deprecation
220+
warning will be issued. In 7.0 it will be required for the `path` to point to a correct
221+
`geo_point` field.
222+
218223
==== Limiting the max number of expansion of span_multi queries
219224

220225
`span_multi` queries will hit too many clauses failure if the number of terms that match the
@@ -223,4 +228,3 @@ can set the <<query-dsl-multi-term-rewrite, rewrite method>> of the multi term q
223228
rewrite. Or, if you use `span_multi` on `prefix` query only, you can activate the
224229
<<index-prefix-config,`index_prefixes`>> field option of the `text` field instead. This will
225230
rewrite any prefix query on the field to a a single term query that matches the indexed prefix.
226-

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

+3
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
import org.elasticsearch.indices.InvalidTypeNameException;
5454
import org.elasticsearch.indices.TypeMissingException;
5555
import org.elasticsearch.indices.mapper.MapperRegistry;
56+
import org.elasticsearch.search.suggest.completion.context.ContextMapping;
5657

5758
import java.io.Closeable;
5859
import java.io.IOException;
@@ -454,6 +455,8 @@ private synchronized Map<String, DocumentMapper> internalMerge(@Nullable Documen
454455
MapperMergeValidator.validateFieldReferences(indexSettings, fieldMappers,
455456
fieldAliasMappers, fullPathObjectMappers, fieldTypes);
456457

458+
ContextMapping.validateContextPaths(indexSettings.getIndexVersionCreated(), fieldMappers, fieldTypes::get);
459+
457460
if (reason == MergeReason.MAPPING_UPDATE) {
458461
// this check will only be performed on the master node when there is
459462
// a call to the update mapping API. For all other cases like

server/src/main/java/org/elasticsearch/search/suggest/completion/context/ContextMapping.java

+29
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
package org.elasticsearch.search.suggest.completion.context;
2121

2222
import org.elasticsearch.ElasticsearchParseException;
23+
import org.elasticsearch.Version;
2324
import org.elasticsearch.common.Strings;
2425
import org.elasticsearch.common.xcontent.ToXContent;
2526
import org.elasticsearch.common.xcontent.ToXContentFragment;
@@ -28,13 +29,16 @@
2829
import org.elasticsearch.common.xcontent.XContentParser.Token;
2930
import org.elasticsearch.common.xcontent.json.JsonXContent;
3031
import org.elasticsearch.index.mapper.CompletionFieldMapper;
32+
import org.elasticsearch.index.mapper.FieldMapper;
33+
import org.elasticsearch.index.mapper.MappedFieldType;
3134
import org.elasticsearch.index.mapper.ParseContext;
3235

3336
import java.io.IOException;
3437
import java.util.ArrayList;
3538
import java.util.List;
3639
import java.util.Objects;
3740
import java.util.Set;
41+
import java.util.function.Function;
3842

3943
/**
4044
* A {@link ContextMapping} defines criteria that can be used to
@@ -131,6 +135,31 @@ public final List<InternalQueryContext> parseQueryContext(XContentParser parser)
131135
*/
132136
protected abstract XContentBuilder toInnerXContent(XContentBuilder builder, Params params) throws IOException;
133137

138+
/**
139+
* Checks if the current context is consistent with the rest of the fields. For example, the GeoContext
140+
* should check that the field that it points to has the correct type.
141+
*/
142+
protected void validateReferences(Version indexVersionCreated, Function<String, MappedFieldType> fieldResolver) {
143+
// No validation is required by default
144+
}
145+
146+
/**
147+
* Verifies that all field paths specified in contexts point to the fields with correct mappings
148+
*/
149+
public static void validateContextPaths(Version indexVersionCreated, List<FieldMapper> fieldMappers,
150+
Function<String, MappedFieldType> fieldResolver) {
151+
for (FieldMapper fieldMapper : fieldMappers) {
152+
if (CompletionFieldMapper.CONTENT_TYPE.equals(fieldMapper.typeName())) {
153+
CompletionFieldMapper.CompletionFieldType fieldType = ((CompletionFieldMapper) fieldMapper).fieldType();
154+
if (fieldType.hasContextMappings()) {
155+
for (ContextMapping context : fieldType.getContextMappings()) {
156+
context.validateReferences(indexVersionCreated, fieldResolver);
157+
}
158+
}
159+
}
160+
}
161+
}
162+
134163
@Override
135164
public final XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
136165
builder.field(FIELD_NAME, name);

server/src/main/java/org/elasticsearch/search/suggest/completion/context/ContextMappings.java

+7-1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import java.util.Collections;
4040
import java.util.HashMap;
4141
import java.util.HashSet;
42+
import java.util.Iterator;
4243
import java.util.List;
4344
import java.util.Map;
4445
import java.util.Objects;
@@ -52,7 +53,7 @@
5253
* and creates context queries for defined {@link ContextMapping}s
5354
* for a {@link CompletionFieldMapper}
5455
*/
55-
public class ContextMappings implements ToXContent {
56+
public class ContextMappings implements ToXContent, Iterable<ContextMapping> {
5657

5758
private static final DeprecationLogger DEPRECATION_LOGGER =
5859
new DeprecationLogger(Loggers.getLogger(ContextMappings.class));
@@ -102,6 +103,11 @@ public void addField(ParseContext.Document document, String name, String input,
102103
document.add(new TypedContextField(name, input, weight, contexts, document));
103104
}
104105

106+
@Override
107+
public Iterator<ContextMapping> iterator() {
108+
return contextMappings.iterator();
109+
}
110+
105111
/**
106112
* Field prepends context values with a suggestion
107113
* Context values are associated with a type, denoted by

server/src/main/java/org/elasticsearch/search/suggest/completion/context/GeoContextMapping.java

+26-3
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,17 @@
1919

2020
package org.elasticsearch.search.suggest.completion.context;
2121

22+
import org.apache.logging.log4j.LogManager;
23+
import org.apache.lucene.document.LatLonDocValuesField;
24+
import org.apache.lucene.document.LatLonPoint;
2225
import org.apache.lucene.document.StringField;
2326
import org.apache.lucene.index.DocValuesType;
2427
import org.apache.lucene.index.IndexableField;
2528
import org.elasticsearch.ElasticsearchParseException;
29+
import org.elasticsearch.Version;
2630
import org.elasticsearch.common.geo.GeoPoint;
2731
import org.elasticsearch.common.geo.GeoUtils;
32+
import org.elasticsearch.common.logging.DeprecationLogger;
2833
import org.elasticsearch.common.unit.DistanceUnit;
2934
import org.elasticsearch.common.xcontent.XContentBuilder;
3035
import org.elasticsearch.common.xcontent.XContentParser;
@@ -42,6 +47,7 @@
4247
import java.util.Map;
4348
import java.util.Objects;
4449
import java.util.Set;
50+
import java.util.function.Function;
4551
import java.util.stream.Collectors;
4652

4753
import static org.elasticsearch.common.geo.GeoHashUtils.addNeighbors;
@@ -69,6 +75,8 @@ public class GeoContextMapping extends ContextMapping<GeoQueryContext> {
6975
static final String CONTEXT_PRECISION = "precision";
7076
static final String CONTEXT_NEIGHBOURS = "neighbours";
7177

78+
private static final DeprecationLogger DEPRECATION_LOGGER = new DeprecationLogger(LogManager.getLogger(GeoContextMapping.class));
79+
7280
private final int precision;
7381
private final String fieldName;
7482

@@ -205,11 +213,11 @@ public Set<CharSequence> parseContext(Document document) {
205213
for (IndexableField field : fields) {
206214
if (field instanceof StringField) {
207215
spare.resetFromString(field.stringValue());
208-
} else {
209-
// todo return this to .stringValue() once LatLonPoint implements it
216+
geohashes.add(spare.geohash());
217+
} else if (field instanceof LatLonPoint || field instanceof LatLonDocValuesField) {
210218
spare.resetFromIndexableField(field);
219+
geohashes.add(spare.geohash());
211220
}
212-
geohashes.add(spare.geohash());
213221
}
214222
}
215223
}
@@ -279,6 +287,21 @@ public List<InternalQueryContext> toInternalQueryContexts(List<GeoQueryContext>
279287
return internalQueryContextList;
280288
}
281289

290+
@Override
291+
protected void validateReferences(Version indexVersionCreated, Function<String, MappedFieldType> fieldResolver) {
292+
if (fieldName != null) {
293+
MappedFieldType mappedFieldType = fieldResolver.apply(fieldName);
294+
if (mappedFieldType == null) {
295+
DEPRECATION_LOGGER.deprecatedAndMaybeLog("geo_context_mapping",
296+
"field [{}] referenced in context [{}] is not defined in the mapping", fieldName, name);
297+
} else if (GeoPointFieldMapper.CONTENT_TYPE.equals(mappedFieldType.typeName()) == false) {
298+
DEPRECATION_LOGGER.deprecatedAndMaybeLog("geo_context_mapping",
299+
"field [{}] referenced in context [{}] must be mapped to geo_point, found [{}]",
300+
fieldName, name, mappedFieldType.typeName());
301+
}
302+
}
303+
}
304+
282305
@Override
283306
public boolean equals(Object o) {
284307
if (this == o) return true;

server/src/test/java/org/elasticsearch/search/suggest/ContextCompletionSuggestSearchIT.java

+17-3
Original file line numberDiff line numberDiff line change
@@ -581,15 +581,24 @@ public void testGeoNeighbours() throws Exception {
581581
}
582582

583583
public void testGeoField() throws Exception {
584-
// Version version = VersionUtils.randomVersionBetween(random(), Version.V_2_0_0, Version.V_5_0_0_alpha5);
585-
// Settings settings = Settings.builder().put(IndexMetaData.SETTING_VERSION_CREATED, version).build();
586584
XContentBuilder mapping = jsonBuilder();
587585
mapping.startObject();
588586
mapping.startObject(TYPE);
589587
mapping.startObject("properties");
588+
mapping.startObject("location");
589+
mapping.startObject("properties");
590590
mapping.startObject("pin");
591591
mapping.field("type", "geo_point");
592+
// Enable store and disable indexing sometimes
593+
if (randomBoolean()) {
594+
mapping.field("store", "true");
595+
}
596+
if (randomBoolean()) {
597+
mapping.field("index", "false");
598+
}
599+
mapping.endObject(); // pin
592600
mapping.endObject();
601+
mapping.endObject(); // location
593602
mapping.startObject(FIELD);
594603
mapping.field("type", "completion");
595604
mapping.field("analyzer", "simple");
@@ -598,7 +607,7 @@ public void testGeoField() throws Exception {
598607
mapping.startObject();
599608
mapping.field("name", "st");
600609
mapping.field("type", "geo");
601-
mapping.field("path", "pin");
610+
mapping.field("path", "location.pin");
602611
mapping.field("precision", 5);
603612
mapping.endObject();
604613
mapping.endArray();
@@ -612,7 +621,9 @@ public void testGeoField() throws Exception {
612621

613622
XContentBuilder source1 = jsonBuilder()
614623
.startObject()
624+
.startObject("location")
615625
.latlon("pin", 52.529172, 13.407333)
626+
.endObject()
616627
.startObject(FIELD)
617628
.array("input", "Hotel Amsterdam in Berlin")
618629
.endObject()
@@ -621,7 +632,9 @@ public void testGeoField() throws Exception {
621632

622633
XContentBuilder source2 = jsonBuilder()
623634
.startObject()
635+
.startObject("location")
624636
.latlon("pin", 52.363389, 4.888695)
637+
.endObject()
625638
.startObject(FIELD)
626639
.array("input", "Hotel Berlin in Amsterdam")
627640
.endObject()
@@ -693,6 +706,7 @@ public void assertSuggestions(String suggestionName, SuggestionBuilder suggestBu
693706
private void createIndexAndMapping(CompletionMappingBuilder completionMappingBuilder) throws IOException {
694707
createIndexAndMappingAndSettings(Settings.EMPTY, completionMappingBuilder);
695708
}
709+
696710
private void createIndexAndMappingAndSettings(Settings settings, CompletionMappingBuilder completionMappingBuilder) throws IOException {
697711
XContentBuilder mapping = jsonBuilder().startObject()
698712
.startObject(TYPE).startObject("properties")

server/src/test/java/org/elasticsearch/search/suggest/completion/GeoContextMappingTests.java

+61
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,67 @@ public void testIndexingWithMultipleContexts() throws Exception {
203203
assertContextSuggestFields(fields, 3);
204204
}
205205

206+
public void testMalformedGeoField() throws Exception {
207+
XContentBuilder mapping = jsonBuilder();
208+
mapping.startObject();
209+
mapping.startObject("type1");
210+
mapping.startObject("properties");
211+
String type = randomFrom("text", "keyword", "long", null);
212+
if (type != null) {
213+
mapping.startObject("pin");
214+
mapping.field("type", type);
215+
mapping.endObject();
216+
}
217+
mapping.startObject("suggestion");
218+
mapping.field("type", "completion");
219+
mapping.field("analyzer", "simple");
220+
221+
mapping.startArray("contexts");
222+
mapping.startObject();
223+
mapping.field("name", "st");
224+
mapping.field("type", "geo");
225+
mapping.field("path", "pin");
226+
mapping.field("precision", 5);
227+
mapping.endObject();
228+
mapping.endArray();
229+
230+
mapping.endObject();
231+
232+
mapping.endObject();
233+
mapping.endObject();
234+
mapping.endObject();
235+
236+
MapperService mapperService = createIndex("test", Settings.EMPTY, "type1", mapping).mapperService();
237+
XContentBuilder builder = jsonBuilder().startObject();
238+
if (type != null) {
239+
switch (type) {
240+
case "keyword":
241+
case "text":
242+
builder.field("pin", "52.529172, 13.407333");
243+
break;
244+
case "long":
245+
builder.field("pin", 1234);
246+
break;
247+
case "object":
248+
builder.latlon("pin", 52.529172, 13.407333);
249+
break;
250+
}
251+
} else {
252+
builder.field("pin", "52.529172, 13.407333");
253+
}
254+
255+
builder.startObject("suggestion")
256+
.array("input", "Hotel Amsterdam in Berlin")
257+
.endObject()
258+
.endObject();
259+
260+
ParsedDocument parsedDocument = mapperService.documentMapper("type1").parse(
261+
SourceToParse.source("test", "type1", "1", BytesReference.bytes(builder), XContentType.JSON));
262+
IndexableField[] fields = parsedDocument.rootDoc().getFields("suggestion");
263+
// Make sure that in 6.x all these cases are still parsed
264+
assertContextSuggestFields(fields, 1);
265+
}
266+
206267
public void testParsingQueryContextBasic() throws Exception {
207268
XContentBuilder builder = jsonBuilder().value("ezs42e44yx96");
208269
XContentParser parser = createParser(JsonXContent.jsonXContent, BytesReference.bytes(builder));

0 commit comments

Comments
 (0)