Added support for nested sorting for script sorting and geo sorting.
Closes #3044
This commit is contained in:
parent
42d5bdd337
commit
db421742f7
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue