Added support for nested sorting for script sorting and geo sorting.

Closes #3044
This commit is contained in:
Martijn van Groningen 2013-05-16 18:45:00 +02:00
parent 42d5bdd337
commit db421742f7
9 changed files with 520 additions and 22 deletions

View File

@ -32,7 +32,7 @@ import java.io.IOException;
*
*/
// LUCENE MONITOR: Monitor against FieldComparator.Double
public class DoubleScriptDataComparator extends FieldComparator<Double> {
public class DoubleScriptDataComparator extends NumberComparatorBase<Double> {
public static IndexFieldData.XFieldComparatorSource comparatorSource(SearchScript script) {
return new InnerSource(script);
@ -126,4 +126,25 @@ public class DoubleScriptDataComparator extends FieldComparator<Double> {
public Double value(int slot) {
return values[slot];
}
@Override
public void add(int slot, int doc) {
script.setNextDocId(doc);
values[slot] += script.runAsDouble();
}
@Override
public void divide(int slot, int divisor) {
values[slot] /= divisor;
}
@Override
public void missing(int slot) {
values[slot] = Double.MAX_VALUE;
}
@Override
public int compareBottomMissing() {
return Double.compare(bottom, Double.MAX_VALUE);
}
}

View File

@ -31,7 +31,7 @@ import java.io.IOException;
/**
*/
public class GeoDistanceComparator extends FieldComparator<Double> {
public class GeoDistanceComparator extends NumberComparatorBase<Double> {
protected final IndexGeoPointFieldData<?> indexFieldData;
@ -121,6 +121,26 @@ public class GeoDistanceComparator extends FieldComparator<Double> {
return values[slot];
}
@Override
public void add(int slot, int doc) {
values[slot] += geoDistanceValues.computeDistance(doc);
}
@Override
public void divide(int slot, int divisor) {
values[slot] /= divisor;
}
@Override
public void missing(int slot) {
values[slot] = Double.MAX_VALUE;
}
@Override
public int compareBottomMissing() {
return Double.compare(bottom, Double.MAX_VALUE);
}
// Computes the distance based on geo points.
// Due to this abstractions the geo distance comparator doesn't need to deal with whether fields have one
// or multiple geo points per document.

View File

@ -64,7 +64,7 @@ public class NestedFieldComparatorSource extends IndexFieldData.XFieldComparator
return new NestedFieldComparator.Avg((NumberComparatorBase) wrappedComparator, rootDocumentsFilter, innerDocumentsFilter, numHits);
default:
throw new ElasticSearchIllegalArgumentException(
String.format("Unsupported sort_mode[%s] for nested type", sortMode)
String.format("Unsupported sort_mode[%s] for nested type", sortMode)
);
}
}

View File

@ -22,6 +22,7 @@ package org.elasticsearch.search.sort;
import org.elasticsearch.common.geo.GeoDistance;
import org.elasticsearch.common.unit.DistanceUnit;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.index.query.FilterBuilder;
import java.io.IOException;
@ -40,6 +41,8 @@ public class GeoDistanceSortBuilder extends SortBuilder {
private DistanceUnit unit;
private SortOrder order;
private String sortMode;
private FilterBuilder nestedFilter;
private String nestedPath;
/**
* Constructs a new distance based sort on a geo point like field.
@ -107,11 +110,29 @@ public class GeoDistanceSortBuilder extends SortBuilder {
* Defines which distance to use for sorting in the case a document contains multiple geo points.
* Possible values: min and max
*/
public SortBuilder sortMode(String sortMode) {
public GeoDistanceSortBuilder sortMode(String sortMode) {
this.sortMode = sortMode;
return this;
}
/**
* Sets the nested filter that the nested objects should match with in order to be taken into account
* for sorting.
*/
public GeoDistanceSortBuilder setNestedFilter(FilterBuilder nestedFilter) {
this.nestedFilter = nestedFilter;
return this;
}
/**
* Sets the nested path if sorting occurs on a field that is inside a nested object. By default when sorting on a
* field inside a nested object, the nearest upper nested object is selected as nested path.
*/
public GeoDistanceSortBuilder setNestedPath(String nestedPath) {
this.nestedPath = nestedPath;
return this;
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject("_geo_distance");
@ -135,6 +156,13 @@ public class GeoDistanceSortBuilder extends SortBuilder {
builder.field("mode", sortMode);
}
if (nestedPath != null) {
builder.field("nested_path", nestedPath);
}
if (nestedFilter != null) {
builder.field("nested_filter", nestedFilter, params);
}
builder.endObject();
return builder;
}

View File

@ -19,6 +19,7 @@
package org.elasticsearch.search.sort;
import org.apache.lucene.search.Filter;
import org.apache.lucene.search.SortField;
import org.elasticsearch.ElasticSearchIllegalArgumentException;
import org.elasticsearch.common.geo.GeoDistance;
@ -27,11 +28,16 @@ import org.elasticsearch.common.geo.GeoPoint;
import org.elasticsearch.common.geo.GeoUtils;
import org.elasticsearch.common.unit.DistanceUnit;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.index.fielddata.IndexFieldData;
import org.elasticsearch.index.fielddata.IndexGeoPointFieldData;
import org.elasticsearch.index.fielddata.fieldcomparator.GeoDistanceComparatorSource;
import org.elasticsearch.index.fielddata.fieldcomparator.SortMode;
import org.elasticsearch.index.mapper.FieldMapper;
import org.elasticsearch.index.mapper.ObjectMappers;
import org.elasticsearch.index.mapper.geo.GeoPointFieldMapper;
import org.elasticsearch.index.mapper.object.ObjectMapper;
import org.elasticsearch.index.search.nested.NestedFieldComparatorSource;
import org.elasticsearch.index.search.nested.NonNestedDocsFilter;
import org.elasticsearch.search.internal.SearchContext;
/**
@ -52,6 +58,8 @@ public class GeoDistanceSortParser implements SortParser {
GeoDistance geoDistance = GeoDistance.ARC;
boolean reverse = false;
SortMode sortMode = null;
String nestedPath = null;
Filter nestedFilter = null;
boolean normalizeLon = true;
boolean normalizeLat = true;
@ -72,17 +80,21 @@ public class GeoDistanceSortParser implements SortParser {
fieldName = currentName;
} else if (token == XContentParser.Token.START_OBJECT) {
// the json in the format of -> field : { lat : 30, lon : 12 }
fieldName = currentName;
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == XContentParser.Token.FIELD_NAME) {
currentName = parser.currentName();
} else if (token.isValue()) {
if (currentName.equals(GeoPointFieldMapper.Names.LAT)) {
point.resetLat(parser.doubleValue());
} else if (currentName.equals(GeoPointFieldMapper.Names.LON)) {
point.resetLon(parser.doubleValue());
} else if (currentName.equals(GeoPointFieldMapper.Names.GEOHASH)) {
GeoHashUtils.decode(parser.text(), point);
if ("nested_filter".equals(currentName) || "nestedFilter".equals(currentName)) {
nestedFilter = context.queryParserService().parseInnerFilter(parser);
} else {
fieldName = currentName;
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == XContentParser.Token.FIELD_NAME) {
currentName = parser.currentName();
} else if (token.isValue()) {
if (currentName.equals(GeoPointFieldMapper.Names.LAT)) {
point.resetLat(parser.doubleValue());
} else if (currentName.equals(GeoPointFieldMapper.Names.LON)) {
point.resetLon(parser.doubleValue());
} else if (currentName.equals(GeoPointFieldMapper.Names.GEOHASH)) {
GeoHashUtils.decode(parser.text(), point);
}
}
}
}
@ -100,6 +112,8 @@ public class GeoDistanceSortParser implements SortParser {
normalizeLon = parser.booleanValue();
} else if ("mode".equals(currentName)) {
sortMode = SortMode.fromString(parser.text());
} else if ("nested_path".equals(currentName) || "nestedPath".equals(currentName)) {
nestedPath = parser.text();
} else {
point.resetFromString(parser.text());
fieldName = currentName;
@ -125,6 +139,35 @@ public class GeoDistanceSortParser implements SortParser {
}
IndexGeoPointFieldData indexFieldData = context.fieldData().getForField(mapper);
return new SortField(fieldName, new GeoDistanceComparatorSource(indexFieldData, point.lat(), point.lon(), unit, geoDistance, sortMode), reverse);
IndexFieldData.XFieldComparatorSource geoDistanceComparatorSource = new GeoDistanceComparatorSource(
indexFieldData, point.lat(), point.lon(), unit, geoDistance, sortMode
);
ObjectMapper objectMapper;
if (nestedPath != null) {
ObjectMappers objectMappers = context.mapperService().objectMapper(nestedPath);
if (objectMappers == null) {
throw new ElasticSearchIllegalArgumentException("failed to find nested object mapping for explicit nested path [" + nestedPath + "]");
}
objectMapper = objectMappers.mapper();
if (!objectMapper.nested().isNested()) {
throw new ElasticSearchIllegalArgumentException("mapping for explicit nested path is not mapped as nested: [" + nestedPath + "]");
}
} else {
objectMapper = context.mapperService().resolveClosestNestedObjectMapper(fieldName);
}
if (objectMapper != null && objectMapper.nested().isNested()) {
Filter rootDocumentsFilter = context.filterCache().cache(NonNestedDocsFilter.INSTANCE);
Filter innerDocumentsFilter;
if (nestedFilter != null) {
innerDocumentsFilter = context.filterCache().cache(nestedFilter);
} else {
innerDocumentsFilter = context.filterCache().cache(objectMapper.nestedTypeFilter());
}
geoDistanceComparatorSource = new NestedFieldComparatorSource(
sortMode, geoDistanceComparatorSource, rootDocumentsFilter, innerDocumentsFilter
);
}
return new SortField(fieldName, geoDistanceComparatorSource, reverse);
}
}

View File

@ -21,6 +21,7 @@ package org.elasticsearch.search.sort;
import com.google.common.collect.Maps;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.index.query.FilterBuilder;
import java.io.IOException;
import java.util.Map;
@ -40,6 +41,12 @@ public class ScriptSortBuilder extends SortBuilder {
private Map<String, Object> params;
private String sortMode;
private FilterBuilder nestedFilter;
private String nestedPath;
/**
* Constructs a script sort builder with the script and the type.
*
@ -100,6 +107,33 @@ public class ScriptSortBuilder extends SortBuilder {
return this;
}
/**
* Defines which distance to use for sorting in the case a document contains multiple geo points.
* Possible values: min and max
*/
public ScriptSortBuilder sortMode(String sortMode) {
this.sortMode = sortMode;
return this;
}
/**
* Sets the nested filter that the nested objects should match with in order to be taken into account
* for sorting.
*/
public ScriptSortBuilder setNestedFilter(FilterBuilder nestedFilter) {
this.nestedFilter = nestedFilter;
return this;
}
/**
* Sets the nested path if sorting occurs on a field that is inside a nested object. For sorting by script this
* needs to be specified.
*/
public ScriptSortBuilder setNestedPath(String nestedPath) {
this.nestedPath = nestedPath;
return this;
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject("_script");
@ -114,6 +148,15 @@ public class ScriptSortBuilder extends SortBuilder {
if (this.params != null) {
builder.field("params", this.params);
}
if (sortMode != null) {
builder.field("mode", sortMode);
}
if (nestedPath != null) {
builder.field("nested_path", nestedPath);
}
if (nestedFilter != null) {
builder.field("nested_filter", nestedFilter, params);
}
builder.endObject();
return builder;
}

View File

@ -19,11 +19,18 @@
package org.elasticsearch.search.sort;
import org.apache.lucene.search.FieldComparatorSource;
import org.apache.lucene.search.Filter;
import org.apache.lucene.search.SortField;
import org.elasticsearch.ElasticSearchIllegalArgumentException;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.index.fielddata.IndexFieldData;
import org.elasticsearch.index.fielddata.fieldcomparator.DoubleScriptDataComparator;
import org.elasticsearch.index.fielddata.fieldcomparator.SortMode;
import org.elasticsearch.index.fielddata.fieldcomparator.StringScriptDataComparator;
import org.elasticsearch.index.mapper.ObjectMappers;
import org.elasticsearch.index.mapper.object.ObjectMapper;
import org.elasticsearch.index.search.nested.NestedFieldComparatorSource;
import org.elasticsearch.index.search.nested.NonNestedDocsFilter;
import org.elasticsearch.script.SearchScript;
import org.elasticsearch.search.SearchParseException;
import org.elasticsearch.search.internal.SearchContext;
@ -47,6 +54,9 @@ public class ScriptSortParser implements SortParser {
String type = null;
Map<String, Object> params = null;
boolean reverse = false;
SortMode sortMode = null;
String nestedPath = null;
Filter nestedFilter = null;
XContentParser.Token token;
String currentName = parser.currentName();
@ -56,6 +66,8 @@ public class ScriptSortParser implements SortParser {
} else if (token == XContentParser.Token.START_OBJECT) {
if ("params".equals(currentName)) {
params = parser.map();
} else if ("nested_filter".equals(currentName) || "nestedFilter".equals(currentName)) {
nestedFilter = context.queryParserService().parseInnerFilter(parser);
}
} else if (token.isValue()) {
if ("reverse".equals(currentName)) {
@ -68,6 +80,10 @@ public class ScriptSortParser implements SortParser {
type = parser.text();
} else if ("lang".equals(currentName)) {
scriptLang = parser.text();
} else if ("mode".equals(currentName)) {
sortMode = SortMode.fromString(parser.text());
} else if ("nested_path".equals(currentName) || "nestedPath".equals(currentName)) {
nestedPath = parser.text();
}
}
}
@ -79,7 +95,7 @@ public class ScriptSortParser implements SortParser {
throw new SearchParseException(context, "_script sorting requires setting the type of the script");
}
SearchScript searchScript = context.scriptService().search(context.lookup(), scriptLang, script, params);
FieldComparatorSource fieldComparatorSource;
IndexFieldData.XFieldComparatorSource fieldComparatorSource;
if ("string".equals(type)) {
fieldComparatorSource = StringScriptDataComparator.comparatorSource(searchScript);
} else if ("number".equals(type)) {
@ -87,6 +103,37 @@ public class ScriptSortParser implements SortParser {
} else {
throw new SearchParseException(context, "custom script sort type [" + type + "] not supported");
}
if ("string".equals(type) && (sortMode == SortMode.SUM || sortMode == SortMode.AVG)) {
throw new SearchParseException(context, "type [string] doesn't support mode [" + sortMode + "]");
}
if (sortMode == null) {
sortMode = reverse ? SortMode.MAX : SortMode.MIN;
}
// If nested_path is specified, then wrap the `fieldComparatorSource` in a `NestedFieldComparatorSource`
ObjectMapper objectMapper;
if (nestedPath != null) {
ObjectMappers objectMappers = context.mapperService().objectMapper(nestedPath);
if (objectMappers == null) {
throw new ElasticSearchIllegalArgumentException("failed to find nested object mapping for explicit nested path [" + nestedPath + "]");
}
objectMapper = objectMappers.mapper();
if (!objectMapper.nested().isNested()) {
throw new ElasticSearchIllegalArgumentException("mapping for explicit nested path is not mapped as nested: [" + nestedPath + "]");
}
Filter rootDocumentsFilter = context.filterCache().cache(NonNestedDocsFilter.INSTANCE);
Filter innerDocumentsFilter;
if (nestedFilter != null) {
innerDocumentsFilter = context.filterCache().cache(nestedFilter);
} else {
innerDocumentsFilter = context.filterCache().cache(objectMapper.nestedTypeFilter());
}
fieldComparatorSource = new NestedFieldComparatorSource(sortMode, fieldComparatorSource, rootDocumentsFilter, innerDocumentsFilter);
}
return new SortField("_script", fieldComparatorSource, reverse);
}
}

View File

@ -23,6 +23,7 @@ import org.apache.lucene.search.Explanation;
import org.elasticsearch.action.admin.indices.status.IndicesStatusResponse;
import org.elasticsearch.action.delete.DeleteResponse;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.search.SearchPhaseExecutionException;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.search.SearchType;
import org.elasticsearch.client.Client;
@ -48,8 +49,8 @@ import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
import static org.elasticsearch.index.query.FilterBuilders.*;
import static org.elasticsearch.index.query.QueryBuilders.*;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.*;
import static org.testng.AssertJUnit.fail;
@Test
public class SimpleNestedTests extends AbstractNodesTests {
@ -613,6 +614,12 @@ public class SimpleNestedTests extends AbstractNodesTests {
.addMapping("type1", jsonBuilder().startObject().startObject("type1").startObject("properties")
.startObject("nested1")
.field("type", "nested")
.startObject("properties")
.startObject("field1")
.field("type", "long")
.field("store", "yes")
.endObject()
.endObject()
.endObject()
.endObject().endObject().endObject())
.execute().actionGet();
@ -680,6 +687,93 @@ public class SimpleNestedTests extends AbstractNodesTests {
assertThat(searchResponse.getHits().hits()[1].sortValues()[0].toString(), equalTo("4"));
assertThat(searchResponse.getHits().hits()[2].id(), equalTo("2"));
assertThat(searchResponse.getHits().hits()[2].sortValues()[0].toString(), equalTo("2"));
searchResponse = client.prepareSearch("test")
.setTypes("type1")
.setQuery(QueryBuilders.matchAllQuery())
.addSort(SortBuilders.scriptSort("_fields['nested1.field1'].value + 1", "number").setNestedPath("nested1").order(SortOrder.DESC))
.execute().actionGet();
assertThat(searchResponse.getHits().totalHits(), equalTo(3l));
assertThat(searchResponse.getHits().hits()[0].id(), equalTo("1"));
assertThat(searchResponse.getHits().hits()[0].sortValues()[0].toString(), equalTo("6.0"));
assertThat(searchResponse.getHits().hits()[1].id(), equalTo("3"));
assertThat(searchResponse.getHits().hits()[1].sortValues()[0].toString(), equalTo("5.0"));
assertThat(searchResponse.getHits().hits()[2].id(), equalTo("2"));
assertThat(searchResponse.getHits().hits()[2].sortValues()[0].toString(), equalTo("3.0"));
searchResponse = client.prepareSearch("test")
.setTypes("type1")
.setQuery(QueryBuilders.matchAllQuery())
.addSort(SortBuilders.scriptSort("_fields['nested1.field1'].value + 1", "number").setNestedPath("nested1").sortMode("sum").order(SortOrder.DESC))
.execute().actionGet();
// B/c of sum it is actually +2
assertThat(searchResponse.getHits().totalHits(), equalTo(3l));
assertThat(searchResponse.getHits().hits()[0].id(), equalTo("1"));
assertThat(searchResponse.getHits().hits()[0].sortValues()[0].toString(), equalTo("11.0"));
assertThat(searchResponse.getHits().hits()[1].id(), equalTo("3"));
assertThat(searchResponse.getHits().hits()[1].sortValues()[0].toString(), equalTo("9.0"));
assertThat(searchResponse.getHits().hits()[2].id(), equalTo("2"));
assertThat(searchResponse.getHits().hits()[2].sortValues()[0].toString(), equalTo("5.0"));
searchResponse = client.prepareSearch("test")
.setTypes("type1")
.setQuery(QueryBuilders.matchAllQuery())
.addSort(SortBuilders.scriptSort("_fields['nested1.field1'].value", "number")
.setNestedFilter(rangeFilter("nested1.field1").from(1).to(3))
.setNestedPath("nested1").sortMode("avg").order(SortOrder.DESC))
.execute().actionGet();
assertThat(searchResponse.getHits().totalHits(), equalTo(3l));
assertThat(searchResponse.getHits().hits()[0].id(), equalTo("1"));
assertThat(searchResponse.getHits().hits()[0].sortValues()[0].toString(), equalTo(Double.toString(Double.MAX_VALUE)));
assertThat(searchResponse.getHits().hits()[1].id(), equalTo("3"));
assertThat(searchResponse.getHits().hits()[1].sortValues()[0].toString(), equalTo("3.0"));
assertThat(searchResponse.getHits().hits()[2].id(), equalTo("2"));
assertThat(searchResponse.getHits().hits()[2].sortValues()[0].toString(), equalTo("1.5"));
searchResponse = client.prepareSearch("test")
.setTypes("type1")
.setQuery(QueryBuilders.matchAllQuery())
.addSort(SortBuilders.scriptSort("_fields['nested1.field1'].value", "string")
.setNestedPath("nested1").order(SortOrder.DESC))
.execute().actionGet();
assertThat(searchResponse.getHits().totalHits(), equalTo(3l));
assertThat(searchResponse.getHits().hits()[0].id(), equalTo("1"));
assertThat(searchResponse.getHits().hits()[0].sortValues()[0].toString(), equalTo("5"));
assertThat(searchResponse.getHits().hits()[1].id(), equalTo("3"));
assertThat(searchResponse.getHits().hits()[1].sortValues()[0].toString(), equalTo("4"));
assertThat(searchResponse.getHits().hits()[2].id(), equalTo("2"));
assertThat(searchResponse.getHits().hits()[2].sortValues()[0].toString(), equalTo("2"));
searchResponse = client.prepareSearch("test")
.setTypes("type1")
.setQuery(QueryBuilders.matchAllQuery())
.addSort(SortBuilders.scriptSort("_fields['nested1.field1'].value", "string")
.setNestedPath("nested1").order(SortOrder.ASC))
.execute().actionGet();
assertThat(searchResponse.getHits().totalHits(), equalTo(3l));
assertThat(searchResponse.getHits().hits()[0].id(), equalTo("2"));
assertThat(searchResponse.getHits().hits()[0].sortValues()[0].toString(), equalTo("1"));
assertThat(searchResponse.getHits().hits()[1].id(), equalTo("3"));
assertThat(searchResponse.getHits().hits()[1].sortValues()[0].toString(), equalTo("3"));
assertThat(searchResponse.getHits().hits()[2].id(), equalTo("1"));
assertThat(searchResponse.getHits().hits()[2].sortValues()[0].toString(), equalTo("4"));
try {
client.prepareSearch("test")
.setTypes("type1")
.setQuery(QueryBuilders.matchAllQuery())
.addSort(SortBuilders.scriptSort("_fields['nested1.field1'].value", "string")
.setNestedPath("nested1").sortMode("sum").order(SortOrder.ASC))
.execute().actionGet();
fail("SearchPhaseExecutionException should have been thrown");
} catch (SearchPhaseExecutionException e) {
assertThat(e.getMessage(), containsString("type [string] doesn't support mode [SUM]"));
}
}
@Test

View File

@ -37,8 +37,7 @@ import org.testng.annotations.Test;
import static org.elasticsearch.common.settings.ImmutableSettings.settingsBuilder;
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
import static org.elasticsearch.index.query.FilterBuilders.geoDistanceFilter;
import static org.elasticsearch.index.query.FilterBuilders.geoDistanceRangeFilter;
import static org.elasticsearch.index.query.FilterBuilders.*;
import static org.elasticsearch.index.query.QueryBuilders.filteredQuery;
import static org.elasticsearch.index.query.QueryBuilders.matchAllQuery;
import static org.hamcrest.MatcherAssert.assertThat;
@ -508,4 +507,207 @@ public class GeoDistanceTests extends AbstractNodesTests {
Double resultArcDistance6 = searchResponse6.getHits().getHits()[0].getFields().get("distance").getValue();
assertThat(resultArcDistance6, equalTo(GeoDistance.ARC.calculate(source_lat, source_long, target_lat, target_long, DistanceUnit.KILOMETERS)));
}
@Test
public void testDistanceSortingNestedFields() throws Exception {
client.admin().indices().prepareDelete().execute().actionGet();
String mapping = XContentFactory.jsonBuilder().startObject().startObject("company")
.startObject("properties")
.startObject("name").field("type", "string").endObject()
.startObject("branches")
.field("type", "nested")
.startObject("properties")
.startObject("name").field("type", "string").endObject()
.startObject("location").field("type", "geo_point").field("lat_lon", true).endObject()
.endObject()
.endObject()
.endObject()
.endObject().endObject().string();
client.admin().indices().prepareCreate("companies")
.setSettings(settingsBuilder().put("index.number_of_shards", 1).put("index.number_of_replicas", 0))
.addMapping("company", mapping)
.execute().actionGet();
client.admin().cluster().prepareHealth("companies").setWaitForEvents(Priority.LANGUID).setWaitForGreenStatus().execute().actionGet();
client.prepareIndex("companies", "company", "1").setSource(jsonBuilder().startObject()
.field("name", "company 1")
.startArray("branches")
.startObject()
.field("name", "New York")
.startObject("location").field("lat", 40.7143528).field("lon", -74.0059731).endObject()
.endObject()
.endArray()
.endObject()).execute().actionGet();
client.prepareIndex("companies", "company", "2").setSource(jsonBuilder().startObject()
.field("name", "company 2")
.startArray("branches")
.startObject()
.field("name", "Times Square")
.startObject("location").field("lat", 40.759011).field("lon", -73.9844722).endObject() // to NY: 5.286 km
.endObject()
.startObject()
.field("name", "Tribeca")
.startObject("location").field("lat", 40.718266).field("lon", -74.007819).endObject() // to NY: 0.4621 km
.endObject()
.endArray()
.endObject()).execute().actionGet();
client.prepareIndex("companies", "company", "3").setSource(jsonBuilder().startObject()
.field("name", "company 3")
.startArray("branches")
.startObject()
.field("name", "Wall Street")
.startObject("location").field("lat", 40.7051157).field("lon", -74.0088305).endObject() // to NY: 1.055 km
.endObject()
.startObject()
.field("name", "Soho")
.startObject("location").field("lat", 40.7247222).field("lon", -74).endObject() // to NY: 1.258 km
.endObject()
.endArray()
.endObject()).execute().actionGet();
client.prepareIndex("companies", "company", "4").setSource(jsonBuilder().startObject()
.field("name", "company 4")
.startArray("branches")
.startObject()
.field("name", "Greenwich Village")
.startObject("location").field("lat", 40.731033).field("lon", -73.9962255).endObject() // to NY: 2.029 km
.endObject()
.startObject()
.field("name", "Brooklyn")
.startObject("location").field("lat", 40.65).field("lon", -73.95).endObject() // to NY: 8.572 km
.endObject()
.endArray()
.endObject()).execute().actionGet();
client.admin().indices().prepareRefresh().execute().actionGet();
// Order: Asc
SearchResponse searchResponse = client.prepareSearch("companies").setQuery(matchAllQuery())
.addSort(SortBuilders.geoDistanceSort("branches.location").point(40.7143528, -74.0059731).order(SortOrder.ASC))
.execute().actionGet();
assertThat(searchResponse.getHits().getTotalHits(), equalTo(4l));
assertThat(searchResponse.getHits().hits().length, equalTo(4));
assertThat(searchResponse.getHits().getAt(0).id(), equalTo("1"));
assertThat(((Number) searchResponse.getHits().getAt(0).sortValues()[0]).doubleValue(), equalTo(0d));
assertThat(searchResponse.getHits().getAt(1).id(), equalTo("2"));
assertThat(((Number) searchResponse.getHits().getAt(1).sortValues()[0]).doubleValue(), closeTo(0.4621d, 0.01d));
assertThat(searchResponse.getHits().getAt(2).id(), equalTo("3"));
assertThat(((Number) searchResponse.getHits().getAt(2).sortValues()[0]).doubleValue(), closeTo(1.055d, 0.01d));
assertThat(searchResponse.getHits().getAt(3).id(), equalTo("4"));
assertThat(((Number) searchResponse.getHits().getAt(3).sortValues()[0]).doubleValue(), closeTo(2.029d, 0.01d));
// Order: Asc, Mode: max
searchResponse = client.prepareSearch("companies").setQuery(matchAllQuery())
.addSort(SortBuilders.geoDistanceSort("branches.location").point(40.7143528, -74.0059731).order(SortOrder.ASC).sortMode("max"))
.execute().actionGet();
assertThat(searchResponse.getHits().getTotalHits(), equalTo(4l));
assertThat(searchResponse.getHits().hits().length, equalTo(4));
assertThat(searchResponse.getHits().getAt(0).id(), equalTo("1"));
assertThat(((Number) searchResponse.getHits().getAt(0).sortValues()[0]).doubleValue(), equalTo(0d));
assertThat(searchResponse.getHits().getAt(1).id(), equalTo("3"));
assertThat(((Number) searchResponse.getHits().getAt(1).sortValues()[0]).doubleValue(), closeTo(1.258d, 0.01d));
assertThat(searchResponse.getHits().getAt(2).id(), equalTo("2"));
assertThat(((Number) searchResponse.getHits().getAt(2).sortValues()[0]).doubleValue(), closeTo(5.286d, 0.01d));
assertThat(searchResponse.getHits().getAt(3).id(), equalTo("4"));
assertThat(((Number) searchResponse.getHits().getAt(3).sortValues()[0]).doubleValue(), closeTo(8.572d, 0.01d));
// Order: Desc
searchResponse = client.prepareSearch("companies").setQuery(matchAllQuery())
.addSort(SortBuilders.geoDistanceSort("branches.location").point(40.7143528, -74.0059731).order(SortOrder.DESC))
.execute().actionGet();
assertThat(searchResponse.getHits().getTotalHits(), equalTo(4l));
assertThat(searchResponse.getHits().hits().length, equalTo(4));
assertThat(searchResponse.getHits().getAt(0).id(), equalTo("4"));
assertThat(((Number) searchResponse.getHits().getAt(0).sortValues()[0]).doubleValue(), closeTo(8.572d, 0.01d));
assertThat(searchResponse.getHits().getAt(1).id(), equalTo("2"));
assertThat(((Number) searchResponse.getHits().getAt(1).sortValues()[0]).doubleValue(), closeTo(5.286d, 0.01d));
assertThat(searchResponse.getHits().getAt(2).id(), equalTo("3"));
assertThat(((Number) searchResponse.getHits().getAt(2).sortValues()[0]).doubleValue(), closeTo(1.258d, 0.01d));
assertThat(searchResponse.getHits().getAt(3).id(), equalTo("1"));
assertThat(((Number) searchResponse.getHits().getAt(3).sortValues()[0]).doubleValue(), equalTo(0d));
// Order: Desc, Mode: min
searchResponse = client.prepareSearch("companies").setQuery(matchAllQuery())
.addSort(SortBuilders.geoDistanceSort("branches.location").point(40.7143528, -74.0059731).order(SortOrder.DESC).sortMode("min"))
.execute().actionGet();
assertThat(searchResponse.getHits().getTotalHits(), equalTo(4l));
assertThat(searchResponse.getHits().hits().length, equalTo(4));
assertThat(searchResponse.getHits().getAt(0).id(), equalTo("4"));
assertThat(((Number) searchResponse.getHits().getAt(0).sortValues()[0]).doubleValue(), closeTo(2.029d, 0.01d));
assertThat(searchResponse.getHits().getAt(1).id(), equalTo("3"));
assertThat(((Number) searchResponse.getHits().getAt(1).sortValues()[0]).doubleValue(), closeTo(1.055d, 0.01d));
assertThat(searchResponse.getHits().getAt(2).id(), equalTo("2"));
assertThat(((Number) searchResponse.getHits().getAt(2).sortValues()[0]).doubleValue(), closeTo(0.4621d, 0.01d));
assertThat(searchResponse.getHits().getAt(3).id(), equalTo("1"));
assertThat(((Number) searchResponse.getHits().getAt(3).sortValues()[0]).doubleValue(), equalTo(0d));
searchResponse = client.prepareSearch("companies").setQuery(matchAllQuery())
.addSort(SortBuilders.geoDistanceSort("branches.location").point(40.7143528, -74.0059731).sortMode("avg").order(SortOrder.ASC))
.execute().actionGet();
assertThat(searchResponse.getHits().getTotalHits(), equalTo(4l));
assertThat(searchResponse.getHits().hits().length, equalTo(4));
assertThat(searchResponse.getHits().getAt(0).id(), equalTo("1"));
assertThat(((Number) searchResponse.getHits().getAt(0).sortValues()[0]).doubleValue(), equalTo(0d));
assertThat(searchResponse.getHits().getAt(1).id(), equalTo("3"));
assertThat(((Number) searchResponse.getHits().getAt(1).sortValues()[0]).doubleValue(), closeTo(1.157d, 0.01d));
assertThat(searchResponse.getHits().getAt(2).id(), equalTo("2"));
assertThat(((Number) searchResponse.getHits().getAt(2).sortValues()[0]).doubleValue(), closeTo(2.874d, 0.01d));
assertThat(searchResponse.getHits().getAt(3).id(), equalTo("4"));
assertThat(((Number) searchResponse.getHits().getAt(3).sortValues()[0]).doubleValue(), closeTo(5.301d, 0.01d));
searchResponse = client.prepareSearch("companies").setQuery(matchAllQuery())
.addSort(
SortBuilders.geoDistanceSort("branches.location").setNestedPath("branches")
.point(40.7143528, -74.0059731).sortMode("avg").order(SortOrder.DESC)
)
.execute().actionGet();
assertThat(searchResponse.getHits().getTotalHits(), equalTo(4l));
assertThat(searchResponse.getHits().hits().length, equalTo(4));
assertThat(searchResponse.getHits().getAt(0).id(), equalTo("4"));
assertThat(((Number) searchResponse.getHits().getAt(0).sortValues()[0]).doubleValue(), closeTo(5.301d, 0.01d));
assertThat(searchResponse.getHits().getAt(1).id(), equalTo("2"));
assertThat(((Number) searchResponse.getHits().getAt(1).sortValues()[0]).doubleValue(), closeTo(2.874d, 0.01d));
assertThat(searchResponse.getHits().getAt(2).id(), equalTo("3"));
assertThat(((Number) searchResponse.getHits().getAt(2).sortValues()[0]).doubleValue(), closeTo(1.157d, 0.01d));
assertThat(searchResponse.getHits().getAt(3).id(), equalTo("1"));
assertThat(((Number) searchResponse.getHits().getAt(3).sortValues()[0]).doubleValue(), equalTo(0d));
searchResponse = client.prepareSearch("companies").setQuery(matchAllQuery())
.addSort(
SortBuilders.geoDistanceSort("branches.location").setNestedFilter(termFilter("branches.name", "brooklyn"))
.point(40.7143528, -74.0059731).sortMode("avg").order(SortOrder.ASC)
)
.execute().actionGet();
assertThat(searchResponse.getHits().getTotalHits(), equalTo(4l));
assertThat(searchResponse.getHits().hits().length, equalTo(4));
assertThat(searchResponse.getHits().getAt(0).id(), equalTo("4"));
assertThat(((Number) searchResponse.getHits().getAt(0).sortValues()[0]).doubleValue(), closeTo(8.572d, 0.01d));
assertThat(searchResponse.getHits().getAt(1).id(), equalTo("1"));
assertThat(((Number) searchResponse.getHits().getAt(1).sortValues()[0]).doubleValue(), equalTo(Double.MAX_VALUE));
assertThat(searchResponse.getHits().getAt(2).id(), equalTo("2"));
assertThat(((Number) searchResponse.getHits().getAt(2).sortValues()[0]).doubleValue(), equalTo(Double.MAX_VALUE));
assertThat(searchResponse.getHits().getAt(3).id(), equalTo("3"));
assertThat(((Number) searchResponse.getHits().getAt(3).sortValues()[0]).doubleValue(), equalTo(Double.MAX_VALUE));
try {
client.prepareSearch("companies").setQuery(matchAllQuery())
.addSort(SortBuilders.geoDistanceSort("branches.location").point(40.7143528, -74.0059731).sortMode("sum"))
.execute().actionGet();
fail("Expected error");
} catch (SearchPhaseExecutionException e) {
assertThat(e.shardFailures()[0].status(), equalTo(RestStatus.BAD_REQUEST));
}
}
}