Skip to content

Commit 5e1cef9

Browse files
committed
Add support for ignore_unmapped to geo sort (#31153)
Adds support for `ignore_unmapped` parameter in geo distance sorting, which is functionally equivalent to specifying an `unmapped_type` in the field sort. Closes #28152
1 parent ad8915f commit 5e1cef9

File tree

4 files changed

+106
-7
lines changed

4 files changed

+106
-7
lines changed

docs/reference/search/request/sort.asciidoc

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -288,8 +288,9 @@ GET /_search
288288
"pin.location" : [-70, 40],
289289
"order" : "asc",
290290
"unit" : "km",
291-
"mode" : "min",
292-
"distance_type" : "arc"
291+
"mode" : "min",
292+
"distance_type" : "arc",
293+
"ignore_unmapped": true
293294
}
294295
}
295296
],
@@ -317,6 +318,12 @@ GET /_search
317318

318319
The unit to use when computing sort values. The default is `m` (meters).
319320

321+
322+
`ignore_unmapped`::
323+
324+
Indicates if the unmapped field should be treated as a missing value. Setting it to `true` is equivalent to specifying
325+
an `unmapped_type` in the field sort. The default is `false` (unmapped field are causing the search to fail).
326+
320327
NOTE: geo distance sorting does not support configurable missing values: the
321328
distance will always be considered equal to +Infinity+ when a document does not
322329
have values for the field that is used for distance computation.

server/src/main/java/org/elasticsearch/search/sort/GeoDistanceSortBuilder.java

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ public class GeoDistanceSortBuilder extends SortBuilder<GeoDistanceSortBuilder>
8181
private static final ParseField DISTANCE_TYPE_FIELD = new ParseField("distance_type");
8282
private static final ParseField VALIDATION_METHOD_FIELD = new ParseField("validation_method");
8383
private static final ParseField SORTMODE_FIELD = new ParseField("mode", "sort_mode");
84+
private static final ParseField IGNORE_UNMAPPED = new ParseField("ignore_unmapped");
8485

8586
private final String fieldName;
8687
private final List<GeoPoint> points = new ArrayList<>();
@@ -97,6 +98,8 @@ public class GeoDistanceSortBuilder extends SortBuilder<GeoDistanceSortBuilder>
9798

9899
private GeoValidationMethod validation = DEFAULT_VALIDATION;
99100

101+
private boolean ignoreUnmapped = false;
102+
100103
/**
101104
* Constructs a new distance based sort on a geo point like field.
102105
*
@@ -152,6 +155,7 @@ public GeoDistanceSortBuilder(String fieldName, String ... geohashes) {
152155
this.nestedPath = original.nestedPath;
153156
this.validation = original.validation;
154157
this.nestedSort = original.nestedSort;
158+
this.ignoreUnmapped = original.ignoreUnmapped;
155159
}
156160

157161
/**
@@ -171,6 +175,9 @@ public GeoDistanceSortBuilder(StreamInput in) throws IOException {
171175
nestedSort = in.readOptionalWriteable(NestedSortBuilder::new);
172176
}
173177
validation = GeoValidationMethod.readFromStream(in);
178+
if (in.getVersion().onOrAfter(Version.V_6_4_0)) {
179+
ignoreUnmapped = in.readBoolean();
180+
}
174181
}
175182

176183
@Override
@@ -187,6 +194,9 @@ public void writeTo(StreamOutput out) throws IOException {
187194
out.writeOptionalWriteable(nestedSort);
188195
}
189196
validation.writeTo(out);
197+
if (out.getVersion().onOrAfter(Version.V_6_4_0)) {
198+
out.writeBoolean(ignoreUnmapped);
199+
}
190200
}
191201

192202
/**
@@ -374,6 +384,18 @@ public GeoDistanceSortBuilder setNestedSort(final NestedSortBuilder nestedSort)
374384
return this;
375385
}
376386

387+
/**
388+
* Returns true if unmapped geo fields should be treated as located at an infinite distance
389+
*/
390+
public boolean ignoreUnmapped() {
391+
return ignoreUnmapped;
392+
}
393+
394+
public GeoDistanceSortBuilder ignoreUnmapped(boolean ignoreUnmapped) {
395+
this.ignoreUnmapped = ignoreUnmapped;
396+
return this;
397+
}
398+
377399
@Override
378400
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
379401
builder.startObject();
@@ -403,6 +425,7 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws
403425
builder.field(NESTED_FIELD.getPreferredName(), nestedSort);
404426
}
405427
builder.field(VALIDATION_METHOD_FIELD.getPreferredName(), validation);
428+
builder.field(IGNORE_UNMAPPED.getPreferredName(), ignoreUnmapped);
406429

407430
builder.endObject();
408431
builder.endObject();
@@ -434,14 +457,15 @@ public boolean equals(Object object) {
434457
Objects.equals(nestedFilter, other.nestedFilter) &&
435458
Objects.equals(nestedPath, other.nestedPath) &&
436459
Objects.equals(validation, other.validation) &&
437-
Objects.equals(nestedSort, other.nestedSort);
460+
Objects.equals(nestedSort, other.nestedSort) &&
461+
ignoreUnmapped == other.ignoreUnmapped;
438462
}
439463

440464
@Override
441465
public int hashCode() {
442466
return Objects.hash(this.fieldName, this.points, this.geoDistance,
443467
this.unit, this.sortMode, this.order, this.nestedFilter,
444-
this.nestedPath, this.validation, this.nestedSort);
468+
this.nestedPath, this.validation, this.nestedSort, this.ignoreUnmapped);
445469
}
446470

447471
/**
@@ -465,6 +489,7 @@ public static GeoDistanceSortBuilder fromXContent(XContentParser parser, String
465489
String nestedPath = null;
466490
NestedSortBuilder nestedSort = null;
467491
GeoValidationMethod validation = null;
492+
boolean ignoreUnmapped = false;
468493

469494
XContentParser.Token token;
470495
String currentName = parser.currentName();
@@ -509,6 +534,8 @@ public static GeoDistanceSortBuilder fromXContent(XContentParser parser, String
509534
} else if (NESTED_PATH_FIELD.match(currentName, parser.getDeprecationHandler())) {
510535
DEPRECATION_LOGGER.deprecated("[nested_path] has been deprecated in favour of the [nested] parameter");
511536
nestedPath = parser.text();
537+
} else if (IGNORE_UNMAPPED.match(currentName, parser.getDeprecationHandler())) {
538+
ignoreUnmapped = parser.booleanValue();
512539
} else if (token == Token.VALUE_STRING){
513540
if (fieldName != null && fieldName.equals(currentName) == false) {
514541
throw new ParsingException(
@@ -554,6 +581,7 @@ public static GeoDistanceSortBuilder fromXContent(XContentParser parser, String
554581
if (validation != null) {
555582
result.validation(validation);
556583
}
584+
result.ignoreUnmapped(ignoreUnmapped);
557585
return result;
558586
}
559587

@@ -596,8 +624,11 @@ public SortFieldAndFormat build(QueryShardContext context) throws IOException {
596624

597625
MappedFieldType fieldType = context.fieldMapper(fieldName);
598626
if (fieldType == null) {
599-
throw new IllegalArgumentException("failed to find mapper for [" + fieldName
600-
+ "] for geo distance based sort");
627+
if (ignoreUnmapped) {
628+
fieldType = context.getMapperService().unmappedFieldType("geo_point");
629+
} else {
630+
throw new IllegalArgumentException("failed to find mapper for [" + fieldName + "] for geo distance based sort");
631+
}
601632
}
602633
final IndexGeoPointFieldData geoIndexFieldData = context.getForField(fieldType);
603634

server/src/test/java/org/elasticsearch/search/sort/GeoDistanceIT.java

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.hasId;
5050
import static org.hamcrest.Matchers.closeTo;
5151
import static org.hamcrest.Matchers.equalTo;
52+
import static org.hamcrest.Matchers.greaterThan;
5253

5354

5455
public class GeoDistanceIT extends ESIntegTestCase {
@@ -406,4 +407,57 @@ public void testGeoDistanceFilter() throws IOException {
406407
assertHitCount(result, 1);
407408
}
408409

410+
public void testDistanceSortingWithUnmappedField() throws Exception {
411+
XContentBuilder xContentBuilder = XContentFactory.jsonBuilder().startObject().startObject("type1").startObject("properties")
412+
.startObject("locations").field("type", "geo_point");
413+
xContentBuilder.endObject().endObject().endObject().endObject();
414+
assertAcked(prepareCreate("test1").addMapping("type1", xContentBuilder));
415+
assertAcked(prepareCreate("test2"));
416+
ensureGreen();
417+
418+
client().prepareIndex("test1", "type1", "1")
419+
.setSource(jsonBuilder().startObject().array("names", "Times Square", "Tribeca").startArray("locations")
420+
// to NY: 5.286 km
421+
.startObject().field("lat", 40.759011).field("lon", -73.9844722).endObject()
422+
// to NY: 0.4621 km
423+
.startObject().field("lat", 40.718266).field("lon", -74.007819).endObject().endArray().endObject())
424+
.execute().actionGet();
425+
426+
client().prepareIndex("test2", "type1", "2")
427+
.setSource(jsonBuilder().startObject().array("names", "Wall Street", "Soho").endObject())
428+
.execute().actionGet();
429+
430+
refresh();
431+
432+
// Order: Asc
433+
SearchResponse searchResponse = client().prepareSearch("test1", "test2").setQuery(matchAllQuery())
434+
.addSort(SortBuilders.geoDistanceSort("locations", 40.7143528, -74.0059731).ignoreUnmapped(true).order(SortOrder.ASC)).execute()
435+
.actionGet();
436+
437+
assertHitCount(searchResponse, 2);
438+
assertOrderedSearchHits(searchResponse, "1", "2");
439+
assertThat(((Number) searchResponse.getHits().getAt(0).getSortValues()[0]).doubleValue(), closeTo(462.1d, 10d));
440+
assertThat(((Number) searchResponse.getHits().getAt(1).getSortValues()[0]).doubleValue(), equalTo(Double.POSITIVE_INFINITY));
441+
442+
// Order: Desc
443+
searchResponse = client().prepareSearch("test1", "test2").setQuery(matchAllQuery())
444+
.addSort(
445+
SortBuilders.geoDistanceSort("locations", 40.7143528, -74.0059731).ignoreUnmapped(true).order(SortOrder.DESC)
446+
).execute()
447+
.actionGet();
448+
449+
// Doc with missing geo point is first, is consistent with 0.20.x
450+
assertHitCount(searchResponse, 2);
451+
assertOrderedSearchHits(searchResponse, "2", "1");
452+
assertThat(((Number) searchResponse.getHits().getAt(0).getSortValues()[0]).doubleValue(), equalTo(Double.POSITIVE_INFINITY));
453+
assertThat(((Number) searchResponse.getHits().getAt(1).getSortValues()[0]).doubleValue(), closeTo(5286d, 10d));
454+
455+
// Make sure that by default the unmapped fields continue to fail
456+
searchResponse = client().prepareSearch("test1", "test2").setQuery(matchAllQuery())
457+
.addSort( SortBuilders.geoDistanceSort("locations", 40.7143528, -74.0059731).order(SortOrder.DESC)).execute()
458+
.actionGet();
459+
assertThat(searchResponse.getFailedShards(), greaterThan(0));
460+
assertHitCount(searchResponse, 1);
461+
}
462+
409463
}

server/src/test/java/org/elasticsearch/search/sort/GeoDistanceSortBuilderTests.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
import org.elasticsearch.index.query.RangeQueryBuilder;
4848
import org.elasticsearch.search.DocValueFormat;
4949
import org.elasticsearch.search.MultiValueMode;
50+
import org.elasticsearch.test.ESTestCase;
5051
import org.elasticsearch.test.geo.RandomGeoGenerator;
5152

5253
import java.io.IOException;
@@ -121,6 +122,9 @@ public static GeoDistanceSortBuilder randomGeoDistanceSortBuilder() {
121122
}
122123
}
123124
}
125+
if (randomBoolean()) {
126+
result.ignoreUnmapped(result.ignoreUnmapped() == false);
127+
}
124128
return result;
125129
}
126130

@@ -154,7 +158,7 @@ private static GeoDistance geoDistance(GeoDistance original) {
154158
@Override
155159
protected GeoDistanceSortBuilder mutate(GeoDistanceSortBuilder original) throws IOException {
156160
GeoDistanceSortBuilder result = new GeoDistanceSortBuilder(original);
157-
int parameter = randomIntBetween(0, 7);
161+
int parameter = randomIntBetween(0, 8);
158162
switch (parameter) {
159163
case 0:
160164
while (Arrays.deepEquals(original.points(), result.points())) {
@@ -194,6 +198,9 @@ protected GeoDistanceSortBuilder mutate(GeoDistanceSortBuilder original) throws
194198
case 7:
195199
result.validation(randomValueOtherThan(result.validation(), () -> randomFrom(GeoValidationMethod.values())));
196200
break;
201+
case 8:
202+
result.ignoreUnmapped(result.ignoreUnmapped() == false);
203+
break;
197204
}
198205
return result;
199206
}

0 commit comments

Comments
 (0)