diff --git a/src/main/java/org/elasticsearch/index/fielddata/fieldcomparator/DoubleScriptDataComparator.java b/src/main/java/org/elasticsearch/index/fielddata/fieldcomparator/DoubleScriptDataComparator.java index 5d8f10a051c7a..093faf101bc01 100644 --- a/src/main/java/org/elasticsearch/index/fielddata/fieldcomparator/DoubleScriptDataComparator.java +++ b/src/main/java/org/elasticsearch/index/fielddata/fieldcomparator/DoubleScriptDataComparator.java @@ -32,7 +32,7 @@ * */ // LUCENE MONITOR: Monitor against FieldComparator.Double -public class DoubleScriptDataComparator extends FieldComparator { +public class DoubleScriptDataComparator extends NumberComparatorBase { public static IndexFieldData.XFieldComparatorSource comparatorSource(SearchScript script) { return new InnerSource(script); @@ -126,4 +126,25 @@ public void setBottom(final int bottom) { 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); + } } diff --git a/src/main/java/org/elasticsearch/index/fielddata/fieldcomparator/GeoDistanceComparator.java b/src/main/java/org/elasticsearch/index/fielddata/fieldcomparator/GeoDistanceComparator.java index deecee2ddaade..51bb8ec0d80d6 100644 --- a/src/main/java/org/elasticsearch/index/fielddata/fieldcomparator/GeoDistanceComparator.java +++ b/src/main/java/org/elasticsearch/index/fielddata/fieldcomparator/GeoDistanceComparator.java @@ -31,7 +31,7 @@ /** */ -public class GeoDistanceComparator extends FieldComparator { +public class GeoDistanceComparator extends NumberComparatorBase { protected final IndexGeoPointFieldData indexFieldData; @@ -121,6 +121,26 @@ public Double value(int slot) { 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. diff --git a/src/main/java/org/elasticsearch/index/search/nested/NestedFieldComparatorSource.java b/src/main/java/org/elasticsearch/index/search/nested/NestedFieldComparatorSource.java index aebcce3234bcd..108e1a1bd7cb4 100644 --- a/src/main/java/org/elasticsearch/index/search/nested/NestedFieldComparatorSource.java +++ b/src/main/java/org/elasticsearch/index/search/nested/NestedFieldComparatorSource.java @@ -64,7 +64,7 @@ public FieldComparator newComparator(String fieldname, int numHits, int sortP 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) ); } } diff --git a/src/main/java/org/elasticsearch/search/sort/GeoDistanceSortBuilder.java b/src/main/java/org/elasticsearch/search/sort/GeoDistanceSortBuilder.java index f9ebf43c1cfaf..8bbb69438c9c7 100644 --- a/src/main/java/org/elasticsearch/search/sort/GeoDistanceSortBuilder.java +++ b/src/main/java/org/elasticsearch/search/sort/GeoDistanceSortBuilder.java @@ -22,6 +22,7 @@ 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 SortBuilder missing(Object missing) { * 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 XContentBuilder toXContent(XContentBuilder builder, Params params) throws 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; } diff --git a/src/main/java/org/elasticsearch/search/sort/GeoDistanceSortParser.java b/src/main/java/org/elasticsearch/search/sort/GeoDistanceSortParser.java index 90172561ba97c..4f6073cbad54b 100644 --- a/src/main/java/org/elasticsearch/search/sort/GeoDistanceSortParser.java +++ b/src/main/java/org/elasticsearch/search/sort/GeoDistanceSortParser.java @@ -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.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 SortField parse(XContentParser parser, SearchContext context) throws Exce 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 SortField parse(XContentParser parser, SearchContext context) throws Exce 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 SortField parse(XContentParser parser, SearchContext context) throws Exce 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 SortField parse(XContentParser parser, SearchContext context) throws Exce } 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); } } diff --git a/src/main/java/org/elasticsearch/search/sort/ScriptSortBuilder.java b/src/main/java/org/elasticsearch/search/sort/ScriptSortBuilder.java index 3070a7a6f10a7..2624afaa31eac 100644 --- a/src/main/java/org/elasticsearch/search/sort/ScriptSortBuilder.java +++ b/src/main/java/org/elasticsearch/search/sort/ScriptSortBuilder.java @@ -21,6 +21,7 @@ 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 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 ScriptSortBuilder lang(String lang) { 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 XContentBuilder toXContent(XContentBuilder builder, Params params) throws 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; } diff --git a/src/main/java/org/elasticsearch/search/sort/ScriptSortParser.java b/src/main/java/org/elasticsearch/search/sort/ScriptSortParser.java index 1aea4853e0c56..04ddf304524f5 100644 --- a/src/main/java/org/elasticsearch/search/sort/ScriptSortParser.java +++ b/src/main/java/org/elasticsearch/search/sort/ScriptSortParser.java @@ -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 SortField parse(XContentParser parser, SearchContext context) throws Exce String type = null; Map 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 SortField parse(XContentParser parser, SearchContext context) throws Exce } 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 SortField parse(XContentParser parser, SearchContext context) throws Exce 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 SortField parse(XContentParser parser, SearchContext context) throws Exce 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 SortField parse(XContentParser parser, SearchContext context) throws Exce } 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); } } diff --git a/src/test/java/org/elasticsearch/test/integration/nested/SimpleNestedTests.java b/src/test/java/org/elasticsearch/test/integration/nested/SimpleNestedTests.java index 19e6d854d7996..82b7f0e229bbd 100644 --- a/src/test/java/org/elasticsearch/test/integration/nested/SimpleNestedTests.java +++ b/src/test/java/org/elasticsearch/test/integration/nested/SimpleNestedTests.java @@ -23,6 +23,7 @@ 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.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 void testSimpleNestedSorting() throws Exception { .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 void testSimpleNestedSorting() throws Exception { 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 diff --git a/src/test/java/org/elasticsearch/test/integration/search/geo/GeoDistanceTests.java b/src/test/java/org/elasticsearch/test/integration/search/geo/GeoDistanceTests.java index ddb466a68fecd..baec477cbe137 100644 --- a/src/test/java/org/elasticsearch/test/integration/search/geo/GeoDistanceTests.java +++ b/src/test/java/org/elasticsearch/test/integration/search/geo/GeoDistanceTests.java @@ -37,8 +37,7 @@ 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 void distanceScriptTests() throws Exception { 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)); + } + } + } \ No newline at end of file