diff --git a/core/src/main/java/org/elasticsearch/index/fielddata/IndexFieldData.java b/core/src/main/java/org/elasticsearch/index/fielddata/IndexFieldData.java index 0b63dfb8df8..16886bf7447 100644 --- a/core/src/main/java/org/elasticsearch/index/fielddata/IndexFieldData.java +++ b/core/src/main/java/org/elasticsearch/index/fielddata/IndexFieldData.java @@ -36,6 +36,7 @@ import org.apache.lucene.util.BitDocIdSet; import org.apache.lucene.util.BitSet; import org.apache.lucene.util.BytesRef; import org.elasticsearch.common.Nullable; +import org.elasticsearch.common.lucene.search.Queries; import org.elasticsearch.index.IndexComponent; import org.elasticsearch.index.IndexSettings; import org.elasticsearch.index.fielddata.IndexFieldData.XFieldComparatorSource.Nested; @@ -116,6 +117,14 @@ public interface IndexFieldData extends IndexCompone this.innerQuery = innerQuery; } + public Query getInnerQuery() { + return innerQuery; + } + + public BitSetProducer getRootFilter() { + return rootFilter; + } + /** * Get a {@link BitDocIdSet} that matches the root documents. */ diff --git a/core/src/main/java/org/elasticsearch/search/MultiValueMode.java b/core/src/main/java/org/elasticsearch/search/MultiValueMode.java index 231bc8bf3c0..1195644e328 100644 --- a/core/src/main/java/org/elasticsearch/search/MultiValueMode.java +++ b/core/src/main/java/org/elasticsearch/search/MultiValueMode.java @@ -514,42 +514,41 @@ public enum MultiValueMode implements Writeable { * NOTE: Calling the returned instance on docs that are not root docs is illegal * The returned instance can only be evaluate the current and upcoming docs */ - public NumericDocValues select(final SortedNumericDocValues values, final long missingValue, final BitSet rootDocs, final DocIdSetIterator innerDocs, int maxDoc) throws IOException { - if (rootDocs == null || innerDocs == null) { + public NumericDocValues select(final SortedNumericDocValues values, final long missingValue, final BitSet parentDocs, final DocIdSetIterator childDocs, int maxDoc) throws IOException { + if (parentDocs == null || childDocs == null) { return select(DocValues.emptySortedNumeric(maxDoc), missingValue); } return new AbstractNumericDocValues() { - int lastSeenRootDoc = -1; + int lastSeenParentDoc = -1; long lastEmittedValue = missingValue; @Override - public boolean advanceExact(int rootDoc) throws IOException { - assert rootDocs.get(rootDoc) : "can only sort root documents"; - assert rootDoc >= lastSeenRootDoc : "can only evaluate current and upcoming root docs"; - if (rootDoc == lastSeenRootDoc) { + public boolean advanceExact(int parentDoc) throws IOException { + assert parentDoc >= lastSeenParentDoc : "can only evaluate current and upcoming parent docs"; + if (parentDoc == lastSeenParentDoc) { return true; - } else if (rootDoc == 0) { + } else if (parentDoc == 0) { lastEmittedValue = missingValue; return true; } - final int prevRootDoc = rootDocs.prevSetBit(rootDoc - 1); - final int firstNestedDoc; - if (innerDocs.docID() > prevRootDoc) { - firstNestedDoc = innerDocs.docID(); + final int prevParentDoc = parentDocs.prevSetBit(parentDoc - 1); + final int firstChildDoc; + if (childDocs.docID() > prevParentDoc) { + firstChildDoc = childDocs.docID(); } else { - firstNestedDoc = innerDocs.advance(prevRootDoc + 1); + firstChildDoc = childDocs.advance(prevParentDoc + 1); } - lastSeenRootDoc = rootDoc; - lastEmittedValue = pick(values, missingValue, innerDocs, firstNestedDoc, rootDoc); + lastSeenParentDoc = parentDoc; + lastEmittedValue = pick(values, missingValue, childDocs, firstChildDoc, parentDoc); return true; } @Override public int docID() { - return lastSeenRootDoc; + return lastSeenParentDoc; } @Override @@ -624,33 +623,32 @@ public enum MultiValueMode implements Writeable { * NOTE: Calling the returned instance on docs that are not root docs is illegal * The returned instance can only be evaluate the current and upcoming docs */ - public NumericDoubleValues select(final SortedNumericDoubleValues values, final double missingValue, final BitSet rootDocs, final DocIdSetIterator innerDocs, int maxDoc) throws IOException { - if (rootDocs == null || innerDocs == null) { + public NumericDoubleValues select(final SortedNumericDoubleValues values, final double missingValue, final BitSet parentDocs, final DocIdSetIterator childDocs, int maxDoc) throws IOException { + if (parentDocs == null || childDocs == null) { return select(FieldData.emptySortedNumericDoubles(), missingValue); } return new NumericDoubleValues() { - int lastSeenRootDoc = 0; + int lastSeenParentDoc = 0; double lastEmittedValue = missingValue; @Override - public boolean advanceExact(int rootDoc) throws IOException { - assert rootDocs.get(rootDoc) : "can only sort root documents"; - assert rootDoc >= lastSeenRootDoc : "can only evaluate current and upcoming root docs"; - if (rootDoc == lastSeenRootDoc) { + public boolean advanceExact(int parentDoc) throws IOException { + assert parentDoc >= lastSeenParentDoc : "can only evaluate current and upcoming parent docs"; + if (parentDoc == lastSeenParentDoc) { return true; } - final int prevRootDoc = rootDocs.prevSetBit(rootDoc - 1); - final int firstNestedDoc; - if (innerDocs.docID() > prevRootDoc) { - firstNestedDoc = innerDocs.docID(); + final int prevParentDoc = parentDocs.prevSetBit(parentDoc - 1); + final int firstChildDoc; + if (childDocs.docID() > prevParentDoc) { + firstChildDoc = childDocs.docID(); } else { - firstNestedDoc = innerDocs.advance(prevRootDoc + 1); + firstChildDoc = childDocs.advance(prevParentDoc + 1); } - lastSeenRootDoc = rootDoc; - lastEmittedValue = pick(values, missingValue, innerDocs, firstNestedDoc, rootDoc); + lastSeenParentDoc = parentDoc; + lastEmittedValue = pick(values, missingValue, childDocs, firstChildDoc, parentDoc); return true; } @@ -733,8 +731,8 @@ public enum MultiValueMode implements Writeable { * NOTE: Calling the returned instance on docs that are not root docs is illegal * The returned instance can only be evaluate the current and upcoming docs */ - public BinaryDocValues select(final SortedBinaryDocValues values, final BytesRef missingValue, final BitSet rootDocs, final DocIdSetIterator innerDocs, int maxDoc) throws IOException { - if (rootDocs == null || innerDocs == null) { + public BinaryDocValues select(final SortedBinaryDocValues values, final BytesRef missingValue, final BitSet parentDocs, final DocIdSetIterator childDocs, int maxDoc) throws IOException { + if (parentDocs == null || childDocs == null) { return select(FieldData.emptySortedBinary(), missingValue); } final BinaryDocValues selectedValues = select(values, null); @@ -743,27 +741,26 @@ public enum MultiValueMode implements Writeable { final BytesRefBuilder builder = new BytesRefBuilder(); - int lastSeenRootDoc = 0; + int lastSeenParentDoc = 0; BytesRef lastEmittedValue = missingValue; @Override - public boolean advanceExact(int rootDoc) throws IOException { - assert rootDocs.get(rootDoc) : "can only sort root documents"; - assert rootDoc >= lastSeenRootDoc : "can only evaluate current and upcoming root docs"; - if (rootDoc == lastSeenRootDoc) { + public boolean advanceExact(int parentDoc) throws IOException { + assert parentDoc >= lastSeenParentDoc : "can only evaluate current and upcoming root docs"; + if (parentDoc == lastSeenParentDoc) { return true; } - final int prevRootDoc = rootDocs.prevSetBit(rootDoc - 1); - final int firstNestedDoc; - if (innerDocs.docID() > prevRootDoc) { - firstNestedDoc = innerDocs.docID(); + final int prevParentDoc = parentDocs.prevSetBit(parentDoc - 1); + final int firstChildDoc; + if (childDocs.docID() > prevParentDoc) { + firstChildDoc = childDocs.docID(); } else { - firstNestedDoc = innerDocs.advance(prevRootDoc + 1); + firstChildDoc = childDocs.advance(prevParentDoc + 1); } - lastSeenRootDoc = rootDoc; - lastEmittedValue = pick(selectedValues, builder, innerDocs, firstNestedDoc, rootDoc); + lastSeenParentDoc = parentDoc; + lastEmittedValue = pick(selectedValues, builder, childDocs, firstChildDoc, parentDoc); if (lastEmittedValue == null) { lastEmittedValue = missingValue; } @@ -850,8 +847,8 @@ public enum MultiValueMode implements Writeable { * NOTE: Calling the returned instance on docs that are not root docs is illegal * The returned instance can only be evaluate the current and upcoming docs */ - public SortedDocValues select(final SortedSetDocValues values, final BitSet rootDocs, final DocIdSetIterator innerDocs) throws IOException { - if (rootDocs == null || innerDocs == null) { + public SortedDocValues select(final SortedSetDocValues values, final BitSet parentDocs, final DocIdSetIterator childDocs) throws IOException { + if (parentDocs == null || childDocs == null) { return select(DocValues.emptySortedSet()); } final SortedDocValues selectedValues = select(values); @@ -859,7 +856,7 @@ public enum MultiValueMode implements Writeable { return new AbstractSortedDocValues() { int docID = -1; - int lastSeenRootDoc = 0; + int lastSeenParentDoc = 0; int lastEmittedOrd = -1; @Override @@ -873,23 +870,22 @@ public enum MultiValueMode implements Writeable { } @Override - public boolean advanceExact(int rootDoc) throws IOException { - assert rootDocs.get(rootDoc) : "can only sort root documents"; - assert rootDoc >= lastSeenRootDoc : "can only evaluate current and upcoming root docs"; - if (rootDoc == lastSeenRootDoc) { + public boolean advanceExact(int parentDoc) throws IOException { + assert parentDoc >= lastSeenParentDoc : "can only evaluate current and upcoming root docs"; + if (parentDoc == lastSeenParentDoc) { return lastEmittedOrd != -1; } - final int prevRootDoc = rootDocs.prevSetBit(rootDoc - 1); - final int firstNestedDoc; - if (innerDocs.docID() > prevRootDoc) { - firstNestedDoc = innerDocs.docID(); + final int prevParentDoc = parentDocs.prevSetBit(parentDoc - 1); + final int firstChildDoc; + if (childDocs.docID() > prevParentDoc) { + firstChildDoc = childDocs.docID(); } else { - firstNestedDoc = innerDocs.advance(prevRootDoc + 1); + firstChildDoc = childDocs.advance(prevParentDoc + 1); } - docID = lastSeenRootDoc = rootDoc; - lastEmittedOrd = pick(selectedValues, innerDocs, firstNestedDoc, rootDoc); + docID = lastSeenParentDoc = parentDoc; + lastEmittedOrd = pick(selectedValues, childDocs, firstChildDoc, parentDoc); return lastEmittedOrd != -1; } diff --git a/core/src/main/java/org/elasticsearch/search/sort/FieldSortBuilder.java b/core/src/main/java/org/elasticsearch/search/sort/FieldSortBuilder.java index 6086bf36e49..22e656ba387 100644 --- a/core/src/main/java/org/elasticsearch/search/sort/FieldSortBuilder.java +++ b/core/src/main/java/org/elasticsearch/search/sort/FieldSortBuilder.java @@ -19,10 +19,15 @@ package org.elasticsearch.search.sort; +import static org.elasticsearch.search.sort.NestedSortBuilder.NESTED_FIELD; + import org.apache.lucene.search.SortField; +import org.elasticsearch.Version; import org.elasticsearch.common.ParseField; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.common.logging.DeprecationLogger; +import org.elasticsearch.common.logging.Loggers; import org.elasticsearch.common.xcontent.ObjectParser; import org.elasticsearch.common.xcontent.ObjectParser.ValueType; import org.elasticsearch.common.xcontent.XContentBuilder; @@ -45,6 +50,8 @@ import java.util.Objects; * A sort builder to sort based on a document field. */ public class FieldSortBuilder extends SortBuilder { + private static final DeprecationLogger DEPRECATION_LOGGER = new DeprecationLogger(Loggers.getLogger(FieldSortBuilder.class)); + public static final String NAME = "field_sort"; public static final ParseField MISSING = new ParseField("missing"); public static final ParseField SORT_MODE = new ParseField("mode"); @@ -71,6 +78,8 @@ public class FieldSortBuilder extends SortBuilder { private String nestedPath; + private NestedSortBuilder nestedSort; + /** Copy constructor. */ public FieldSortBuilder(FieldSortBuilder template) { this(template.fieldName); @@ -82,6 +91,7 @@ public class FieldSortBuilder extends SortBuilder { } this.setNestedFilter(template.getNestedFilter()); this.setNestedPath(template.getNestedPath()); + this.setNestedSort(template.getNestedSort()); } /** @@ -108,6 +118,9 @@ public class FieldSortBuilder extends SortBuilder { order = in.readOptionalWriteable(SortOrder::readFromStream); sortMode = in.readOptionalWriteable(SortMode::readFromStream); unmappedType = in.readOptionalString(); + if (in.getVersion().onOrAfter(Version.V_7_0_0_alpha1)) { + nestedSort = in.readOptionalWriteable(NestedSortBuilder::new); + } } @Override @@ -119,6 +132,9 @@ public class FieldSortBuilder extends SortBuilder { out.writeOptionalWriteable(order); out.writeOptionalWriteable(sortMode); out.writeOptionalString(unmappedType); + if (out.getVersion().onOrAfter(Version.V_7_0_0_alpha1)) { + out.writeOptionalWriteable(nestedSort); + } } /** Returns the document field this sort should be based on. */ @@ -221,6 +237,15 @@ public class FieldSortBuilder extends SortBuilder { return this.nestedPath; } + public NestedSortBuilder getNestedSort() { + return this.nestedSort; + } + + public FieldSortBuilder setNestedSort(final NestedSortBuilder nestedSort) { + this.nestedSort = nestedSort; + return this; + } + @Override public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { builder.startObject(); @@ -241,6 +266,9 @@ public class FieldSortBuilder extends SortBuilder { if (nestedPath != null) { builder.field(NESTED_PATH_FIELD.getPreferredName(), nestedPath); } + if (nestedSort != null) { + builder.field(NESTED_FIELD.getPreferredName(), nestedSort); + } builder.endObject(); builder.endObject(); return builder; @@ -274,7 +302,14 @@ public class FieldSortBuilder extends SortBuilder { localSortMode = reverse ? MultiValueMode.MAX : MultiValueMode.MIN; } - final Nested nested = resolveNested(context, nestedPath, nestedFilter); + final Nested nested; + if (nestedSort != null) { + // new nested sorts takes priority + nested = resolveNested(context, nestedSort); + } else { + nested = resolveNested(context, nestedPath, nestedFilter); + } + IndexFieldData fieldData = context.getForField(fieldType); if (fieldData instanceof IndexNumericFieldData == false && (sortMode == SortMode.SUM || sortMode == SortMode.AVG || sortMode == SortMode.MEDIAN)) { @@ -299,12 +334,13 @@ public class FieldSortBuilder extends SortBuilder { return (Objects.equals(this.fieldName, builder.fieldName) && Objects.equals(this.nestedFilter, builder.nestedFilter) && Objects.equals(this.nestedPath, builder.nestedPath) && Objects.equals(this.missing, builder.missing) && Objects.equals(this.order, builder.order) && Objects.equals(this.sortMode, builder.sortMode) - && Objects.equals(this.unmappedType, builder.unmappedType)); + && Objects.equals(this.unmappedType, builder.unmappedType) && Objects.equals(this.nestedSort, builder.nestedSort)); } @Override public int hashCode() { - return Objects.hash(this.fieldName, this.nestedFilter, this.nestedPath, this.missing, this.order, this.sortMode, this.unmappedType); + return Objects.hash(this.fieldName, this.nestedFilter, this.nestedPath, this.nestedSort, this.missing, this.order, this.sortMode, + this.unmappedType); } @Override @@ -329,11 +365,18 @@ public class FieldSortBuilder extends SortBuilder { static { PARSER.declareField(FieldSortBuilder::missing, p -> p.objectText(), MISSING, ValueType.VALUE); - PARSER.declareString(FieldSortBuilder::setNestedPath , NESTED_PATH_FIELD); + PARSER.declareString((fieldSortBuilder, nestedPath) -> { + DEPRECATION_LOGGER.deprecated("[nested_path] has been deprecated in favor of the [nested] parameter"); + fieldSortBuilder.setNestedPath(nestedPath); + }, NESTED_PATH_FIELD); PARSER.declareString(FieldSortBuilder::unmappedType , UNMAPPED_TYPE); PARSER.declareString((b, v) -> b.order(SortOrder.fromString(v)) , ORDER_FIELD); PARSER.declareString((b, v) -> b.sortMode(SortMode.fromString(v)), SORT_MODE); - PARSER.declareObject(FieldSortBuilder::setNestedFilter, (p, c) -> SortBuilder.parseNestedFilter(p), NESTED_FILTER_FIELD); + PARSER.declareObject(FieldSortBuilder::setNestedFilter, (p, c) -> { + DEPRECATION_LOGGER.deprecated("[nested_filter] has been deprecated in favour for the [nested] parameter"); + return SortBuilder.parseNestedFilter(p); + }, NESTED_FILTER_FIELD); + PARSER.declareObject(FieldSortBuilder::setNestedSort, (p, c) -> NestedSortBuilder.fromXContent(p), NESTED_FIELD); } @Override diff --git a/core/src/main/java/org/elasticsearch/search/sort/GeoDistanceSortBuilder.java b/core/src/main/java/org/elasticsearch/search/sort/GeoDistanceSortBuilder.java index 358adf6390e..686b241489b 100644 --- a/core/src/main/java/org/elasticsearch/search/sort/GeoDistanceSortBuilder.java +++ b/core/src/main/java/org/elasticsearch/search/sort/GeoDistanceSortBuilder.java @@ -27,6 +27,7 @@ import org.apache.lucene.search.FieldComparator; import org.apache.lucene.search.SortField; import org.apache.lucene.util.BitSet; import org.elasticsearch.ElasticsearchParseException; +import org.elasticsearch.Version; import org.elasticsearch.common.ParseField; import org.elasticsearch.common.ParsingException; import org.elasticsearch.common.geo.GeoDistance; @@ -34,6 +35,8 @@ import org.elasticsearch.common.geo.GeoPoint; import org.elasticsearch.common.geo.GeoUtils; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.common.logging.DeprecationLogger; +import org.elasticsearch.common.logging.Loggers; import org.elasticsearch.common.unit.DistanceUnit; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentParser; @@ -61,10 +64,14 @@ import java.util.Locale; import java.util.Objects; import static org.elasticsearch.index.query.AbstractQueryBuilder.parseInnerQueryBuilder; +import static org.elasticsearch.search.sort.NestedSortBuilder.NESTED_FIELD; + /** * A geo distance based sorting on a geo point like field. */ public class GeoDistanceSortBuilder extends SortBuilder { + private static final DeprecationLogger DEPRECATION_LOGGER = new DeprecationLogger(Loggers.getLogger(GeoDistanceSortBuilder.class)); + public static final String NAME = "_geo_distance"; public static final String ALTERNATIVE_NAME = "_geoDistance"; public static final GeoValidationMethod DEFAULT_VALIDATION = GeoValidationMethod.DEFAULT; @@ -85,6 +92,8 @@ public class GeoDistanceSortBuilder extends SortBuilder private QueryBuilder nestedFilter; private String nestedPath; + private NestedSortBuilder nestedSort; + private GeoValidationMethod validation = DEFAULT_VALIDATION; /** @@ -141,6 +150,7 @@ public class GeoDistanceSortBuilder extends SortBuilder this.nestedFilter = original.nestedFilter; this.nestedPath = original.nestedPath; this.validation = original.validation; + this.nestedSort = original.nestedSort; } /** @@ -156,6 +166,9 @@ public class GeoDistanceSortBuilder extends SortBuilder sortMode = in.readOptionalWriteable(SortMode::readFromStream); nestedFilter = in.readOptionalNamedWriteable(QueryBuilder.class); nestedPath = in.readOptionalString(); + if (in.getVersion().onOrAfter(Version.V_7_0_0_alpha1)) { + nestedSort = in.readOptionalWriteable(NestedSortBuilder::new); + } validation = GeoValidationMethod.readFromStream(in); } @@ -169,6 +182,9 @@ public class GeoDistanceSortBuilder extends SortBuilder out.writeOptionalWriteable(sortMode); out.writeOptionalNamedWriteable(nestedFilter); out.writeOptionalString(nestedPath); + if (out.getVersion().onOrAfter(Version.V_7_0_0_alpha1)) { + out.writeOptionalWriteable(nestedSort); + } validation.writeTo(out); } @@ -317,6 +333,15 @@ public class GeoDistanceSortBuilder extends SortBuilder return this.nestedPath; } + public NestedSortBuilder getNestedSort() { + return this.nestedSort; + } + + public GeoDistanceSortBuilder setNestedSort(final NestedSortBuilder nestedSort) { + this.nestedSort = nestedSort; + return this; + } + @Override public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { builder.startObject(); @@ -342,6 +367,9 @@ public class GeoDistanceSortBuilder extends SortBuilder if (nestedFilter != null) { builder.field(NESTED_FILTER_FIELD.getPreferredName(), nestedFilter, params); } + if (nestedSort != null) { + builder.field(NESTED_FIELD.getPreferredName(), nestedSort); + } builder.field(VALIDATION_METHOD_FIELD.getPreferredName(), validation); builder.endObject(); @@ -373,14 +401,15 @@ public class GeoDistanceSortBuilder extends SortBuilder Objects.equals(order, other.order) && Objects.equals(nestedFilter, other.nestedFilter) && Objects.equals(nestedPath, other.nestedPath) && - Objects.equals(validation, other.validation); + Objects.equals(validation, other.validation) && + Objects.equals(nestedSort, other.nestedSort); } @Override public int hashCode() { return Objects.hash(this.fieldName, this.points, this.geoDistance, this.unit, this.sortMode, this.order, this.nestedFilter, - this.nestedPath, this.validation); + this.nestedPath, this.validation, this.nestedSort); } /** @@ -402,6 +431,7 @@ public class GeoDistanceSortBuilder extends SortBuilder SortMode sortMode = null; QueryBuilder nestedFilter = null; String nestedPath = null; + NestedSortBuilder nestedSort = null; GeoValidationMethod validation = null; XContentParser.Token token; @@ -415,7 +445,10 @@ public class GeoDistanceSortBuilder extends SortBuilder fieldName = currentName; } else if (token == XContentParser.Token.START_OBJECT) { if (NESTED_FILTER_FIELD.match(currentName)) { + DEPRECATION_LOGGER.deprecated("[nested_filter] has been deprecated in favour for the [nested] parameter"); nestedFilter = parseInnerQueryBuilder(parser); + } else if (NESTED_FIELD.match(currentName)) { + nestedSort = NestedSortBuilder.fromXContent(parser); } else { // the json in the format of -> field : { lat : 30, lon : 12 } if (fieldName != null && fieldName.equals(currentName) == false) { @@ -442,6 +475,7 @@ public class GeoDistanceSortBuilder extends SortBuilder } else if (SORTMODE_FIELD.match(currentName)) { sortMode = SortMode.fromString(parser.text()); } else if (NESTED_PATH_FIELD.match(currentName)) { + DEPRECATION_LOGGER.deprecated("[nested_path] has been deprecated in favor of the [nested] parameter"); nestedPath = parser.text(); } else if (token == Token.VALUE_STRING){ if (fieldName != null && fieldName.equals(currentName) == false) { @@ -482,6 +516,9 @@ public class GeoDistanceSortBuilder extends SortBuilder result.setNestedFilter(nestedFilter); } result.setNestedPath(nestedPath); + if (nestedSort != null) { + result.setNestedSort(nestedSort); + } if (validation != null) { result.validation(validation); } @@ -531,7 +568,14 @@ public class GeoDistanceSortBuilder extends SortBuilder + "] for geo distance based sort"); } final IndexGeoPointFieldData geoIndexFieldData = context.getForField(fieldType); - final Nested nested = resolveNested(context, nestedPath, nestedFilter); + + final Nested nested; + if (nestedSort != null) { + // new nested sorts takes priority + nested = resolveNested(context, nestedSort); + } else { + nested = resolveNested(context, nestedPath, nestedFilter); + } if (geoIndexFieldData.getClass() == LatLonPointDVIndexFieldData.class // only works with 5.x geo_point && nested == null diff --git a/core/src/main/java/org/elasticsearch/search/sort/NestedSortBuilder.java b/core/src/main/java/org/elasticsearch/search/sort/NestedSortBuilder.java new file mode 100644 index 00000000000..bcf5ee681e1 --- /dev/null +++ b/core/src/main/java/org/elasticsearch/search/sort/NestedSortBuilder.java @@ -0,0 +1,176 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.search.sort; + +import static org.elasticsearch.search.sort.SortBuilder.parseNestedFilter; + +import org.elasticsearch.common.ParseField; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.common.io.stream.Writeable; +import org.elasticsearch.common.xcontent.ToXContentObject; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.index.query.QueryBuilder; + +import java.io.IOException; +import java.util.Objects; + +public class NestedSortBuilder implements Writeable, Writeable.Reader, ToXContentObject { + public static final ParseField NESTED_FIELD = new ParseField("nested"); + public static final ParseField PATH_FIELD = new ParseField("path"); + public static final ParseField FILTER_FIELD = new ParseField("filter"); + + private String path; + private QueryBuilder filter; + private NestedSortBuilder nestedSort; + + public NestedSortBuilder(String path) { + this.path = path; + } + + public NestedSortBuilder(StreamInput in) throws IOException { + read(in); + } + + public NestedSortBuilder(NestedSortBuilder other) { + if (other != null) { + this.path = other.path; + this.filter = other.filter; + this.nestedSort = other.nestedSort; + } + } + + public String getPath() { + return path; + } + + public NestedSortBuilder setPath(final String path) { + this.path = path; + return this; + } + + public QueryBuilder getFilter() { + return filter; + } + + public NestedSortBuilder setFilter(final QueryBuilder filter) { + this.filter = filter; + return this; + } + + public NestedSortBuilder getNestedSort() { + return nestedSort; + } + + public NestedSortBuilder setNestedSort(final NestedSortBuilder nestedSortBuilder) { + this.nestedSort = nestedSortBuilder; + return this; + } + + /** + * Write this object's fields to a {@linkplain StreamOutput}. + */ + @Override + public void writeTo(final StreamOutput out) throws IOException { + out.writeOptionalString(path); + out.writeOptionalNamedWriteable(filter); + out.writeOptionalWriteable(nestedSort); + } + + /** + * Read {@code V}-type value from a stream. + * + * @param in Input to read the value from + */ + @Override + public NestedSortBuilder read(final StreamInput in) throws IOException { + path = in.readOptionalString(); + filter = in.readOptionalNamedWriteable(QueryBuilder.class); + nestedSort = in.readOptionalWriteable(NestedSortBuilder::new); + return this; + } + + @Override + public XContentBuilder toXContent(final XContentBuilder builder, final Params params) throws IOException { + builder.startObject(); + if (path != null) { + builder.field(PATH_FIELD.getPreferredName(), path); + } + if (filter != null) { + builder.field(FILTER_FIELD.getPreferredName(), filter); + } + if (nestedSort != null) { + builder.field(NESTED_FIELD.getPreferredName(), nestedSort); + } + builder.endObject(); + return builder; + } + + public static NestedSortBuilder fromXContent(XContentParser parser) throws IOException { + String path = null; + QueryBuilder filter = null; + NestedSortBuilder nestedSort = null; + + XContentParser.Token token = parser.currentToken(); + if (token == XContentParser.Token.START_OBJECT) { + while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { + if (token == XContentParser.Token.FIELD_NAME) { + String currentName = parser.currentName(); + parser.nextToken(); + if (currentName.equals(PATH_FIELD.getPreferredName())) { + path = parser.text(); + } else if (currentName.equals(FILTER_FIELD.getPreferredName())) { + filter = parseNestedFilter(parser); + } else if (currentName.equals(NESTED_FIELD.getPreferredName())) { + nestedSort = NestedSortBuilder.fromXContent(parser); + } else { + throw new IllegalArgumentException("malformed nested sort format, unknown field name [" + currentName + "]"); + } + } else { + throw new IllegalArgumentException("malformed nested sort format, only field names are allowed"); + } + } + } else { + throw new IllegalArgumentException("malformed nested sort format, must start with an object"); + } + + return new NestedSortBuilder(path).setFilter(filter).setNestedSort(nestedSort); + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + NestedSortBuilder that = (NestedSortBuilder) obj; + return Objects.equals(path, that.path) + && Objects.equals(filter, that.filter) + && Objects.equals(nestedSort, that.nestedSort); + } + + @Override + public int hashCode() { + return Objects.hash(path, filter, nestedSort); + } +} diff --git a/core/src/main/java/org/elasticsearch/search/sort/ScriptSortBuilder.java b/core/src/main/java/org/elasticsearch/search/sort/ScriptSortBuilder.java index 1e605dc397a..866ace6313e 100644 --- a/core/src/main/java/org/elasticsearch/search/sort/ScriptSortBuilder.java +++ b/core/src/main/java/org/elasticsearch/search/sort/ScriptSortBuilder.java @@ -25,10 +25,13 @@ import org.apache.lucene.search.Scorer; import org.apache.lucene.search.SortField; import org.apache.lucene.util.BytesRef; import org.apache.lucene.util.BytesRefBuilder; +import org.elasticsearch.Version; import org.elasticsearch.common.ParseField; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.io.stream.Writeable; +import org.elasticsearch.common.logging.DeprecationLogger; +import org.elasticsearch.common.logging.Loggers; import org.elasticsearch.common.xcontent.ConstructingObjectParser; import org.elasticsearch.common.xcontent.ObjectParser.ValueType; import org.elasticsearch.common.xcontent.XContentBuilder; @@ -56,11 +59,13 @@ import java.util.Locale; import java.util.Objects; import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constructorArg; +import static org.elasticsearch.search.sort.NestedSortBuilder.NESTED_FIELD; /** * Script sort builder allows to sort based on a custom script expression. */ public class ScriptSortBuilder extends SortBuilder { + private static final DeprecationLogger DEPRECATION_LOGGER = new DeprecationLogger(Loggers.getLogger(ScriptSortBuilder.class)); public static final String NAME = "_script"; public static final ParseField TYPE_FIELD = new ParseField("type"); @@ -77,6 +82,8 @@ public class ScriptSortBuilder extends SortBuilder { private String nestedPath; + private NestedSortBuilder nestedSort; + /** * Constructs a script sort builder with the given script. * @@ -100,6 +107,7 @@ public class ScriptSortBuilder extends SortBuilder { this.sortMode = original.sortMode; this.nestedFilter = original.nestedFilter; this.nestedPath = original.nestedPath; + this.nestedSort = original.nestedSort; } /** @@ -112,6 +120,9 @@ public class ScriptSortBuilder extends SortBuilder { sortMode = in.readOptionalWriteable(SortMode::readFromStream); nestedPath = in.readOptionalString(); nestedFilter = in.readOptionalNamedWriteable(QueryBuilder.class); + if (in.getVersion().onOrAfter(Version.V_7_0_0_alpha1)) { + nestedSort = in.readOptionalWriteable(NestedSortBuilder::new); + } } @Override @@ -122,6 +133,9 @@ public class ScriptSortBuilder extends SortBuilder { out.writeOptionalWriteable(sortMode); out.writeOptionalString(nestedPath); out.writeOptionalNamedWriteable(nestedFilter); + if (out.getVersion().onOrAfter(Version.V_7_0_0_alpha1)) { + out.writeOptionalWriteable(nestedSort); + } } /** @@ -191,6 +205,15 @@ public class ScriptSortBuilder extends SortBuilder { return this.nestedPath; } + public NestedSortBuilder getNestedSort() { + return this.nestedSort; + } + + public ScriptSortBuilder setNestedSort(final NestedSortBuilder nestedSort) { + this.nestedSort = nestedSort; + return this; + } + @Override public XContentBuilder toXContent(XContentBuilder builder, Params builderParams) throws IOException { builder.startObject(); @@ -207,6 +230,9 @@ public class ScriptSortBuilder extends SortBuilder { if (nestedFilter != null) { builder.field(NESTED_FILTER_FIELD.getPreferredName(), nestedFilter, builderParams); } + if (nestedSort != null) { + builder.field(NESTED_FIELD.getPreferredName(), nestedSort); + } builder.endObject(); builder.endObject(); return builder; @@ -221,8 +247,15 @@ public class ScriptSortBuilder extends SortBuilder { PARSER.declareField(constructorArg(), p -> ScriptSortType.fromString(p.text()), TYPE_FIELD, ValueType.STRING); PARSER.declareString((b, v) -> b.order(SortOrder.fromString(v)), ORDER_FIELD); PARSER.declareString((b, v) -> b.sortMode(SortMode.fromString(v)), SORTMODE_FIELD); - PARSER.declareString(ScriptSortBuilder::setNestedPath , NESTED_PATH_FIELD); - PARSER.declareObject(ScriptSortBuilder::setNestedFilter, (p,c) -> SortBuilder.parseNestedFilter(p), NESTED_FILTER_FIELD); + PARSER.declareString((fieldSortBuilder, nestedPath) -> { + DEPRECATION_LOGGER.deprecated("[nested_path] has been deprecated in favor of the [nested] parameter"); + fieldSortBuilder.setNestedPath(nestedPath); + }, NESTED_PATH_FIELD); + PARSER.declareObject(ScriptSortBuilder::setNestedFilter, (p, c) -> { + DEPRECATION_LOGGER.deprecated("[nested_filter] has been deprecated in favour for the [nested] parameter"); + return SortBuilder.parseNestedFilter(p); + }, NESTED_FILTER_FIELD); + PARSER.declareObject(ScriptSortBuilder::setNestedSort, (p, c) -> NestedSortBuilder.fromXContent(p), NESTED_FIELD); } /** @@ -253,7 +286,14 @@ public class ScriptSortBuilder extends SortBuilder { valueMode = reverse ? MultiValueMode.MAX : MultiValueMode.MIN; } - final Nested nested = resolveNested(context, nestedPath, nestedFilter); + final Nested nested; + if (nestedSort != null) { + // new nested sorts takes priority + nested = resolveNested(context, nestedSort); + } else { + nested = resolveNested(context, nestedPath, nestedFilter); + } + final IndexFieldData.XFieldComparatorSource fieldComparatorSource; switch (type) { case STRING: @@ -329,12 +369,13 @@ public class ScriptSortBuilder extends SortBuilder { Objects.equals(order, other.order) && Objects.equals(sortMode, other.sortMode) && Objects.equals(nestedFilter, other.nestedFilter) && - Objects.equals(nestedPath, other.nestedPath); + Objects.equals(nestedPath, other.nestedPath) && + Objects.equals(nestedSort, other.nestedSort); } @Override public int hashCode() { - return Objects.hash(script, type, order, sortMode, nestedFilter, nestedPath); + return Objects.hash(script, type, order, sortMode, nestedFilter, nestedPath, nestedSort); } @Override diff --git a/core/src/main/java/org/elasticsearch/search/sort/SortBuilder.java b/core/src/main/java/org/elasticsearch/search/sort/SortBuilder.java index 6ea8063a775..024523223be 100644 --- a/core/src/main/java/org/elasticsearch/search/sort/SortBuilder.java +++ b/core/src/main/java/org/elasticsearch/search/sort/SortBuilder.java @@ -22,7 +22,9 @@ package org.elasticsearch.search.sort; import org.apache.lucene.search.Query; import org.apache.lucene.search.Sort; import org.apache.lucene.search.SortField; -import org.apache.lucene.search.join.BitSetProducer; +import org.apache.lucene.search.join.ScoreMode; +import org.apache.lucene.search.join.ToChildBlockJoinQuery; +import org.apache.lucene.search.join.ToParentBlockJoinQuery; import org.elasticsearch.common.ParseField; import org.elasticsearch.common.ParsingException; import org.elasticsearch.common.Strings; @@ -178,28 +180,86 @@ public abstract class SortBuilder> implements NamedWrit } protected static Nested resolveNested(QueryShardContext context, String nestedPath, QueryBuilder nestedFilter) throws IOException { - Nested nested = null; - if (nestedPath != null) { - BitSetProducer rootDocumentsFilter = context.bitsetFilter(Queries.newNonNestedFilter()); - ObjectMapper nestedObjectMapper = context.getObjectMapper(nestedPath); - if (nestedObjectMapper == null) { - throw new QueryShardException(context, "[nested] failed to find nested object under path [" + nestedPath + "]"); - } - if (!nestedObjectMapper.nested().isNested()) { - throw new QueryShardException(context, "[nested] nested object under path [" + nestedPath + "] is not of nested type"); - } - Query innerDocumentsQuery; - if (nestedFilter != null) { - context.nestedScope().nextLevel(nestedObjectMapper); - assert nestedFilter == Rewriteable.rewrite(nestedFilter, context) : "nested filter is not rewritten"; - innerDocumentsQuery = nestedFilter.toFilter(context); - context.nestedScope().previousLevel(); - } else { - innerDocumentsQuery = nestedObjectMapper.nestedTypeFilter(); - } - nested = new Nested(rootDocumentsFilter, innerDocumentsQuery); + NestedSortBuilder nestedSortBuilder = new NestedSortBuilder(nestedPath); + nestedSortBuilder.setFilter(nestedFilter); + + return resolveNested(context, nestedSortBuilder); + } + + protected static Nested resolveNested(QueryShardContext context, NestedSortBuilder nestedSort) throws IOException { + return resolveNested(context, nestedSort, null); + } + + protected static Nested resolveNested(QueryShardContext context, NestedSortBuilder nestedSort, Nested nested) throws IOException { + if (nestedSort == null || nestedSort.getPath() == null) { + return null; + } + + String nestedPath = nestedSort.getPath(); + QueryBuilder nestedFilter = nestedSort.getFilter(); + NestedSortBuilder nestedNestedSort = nestedSort.getNestedSort(); + + // verify our nested path + ObjectMapper nestedObjectMapper = context.getObjectMapper(nestedPath); + + if (nestedObjectMapper == null) { + throw new QueryShardException(context, "[nested] failed to find nested object under path [" + nestedPath + "]"); + } + if (!nestedObjectMapper.nested().isNested()) { + throw new QueryShardException(context, "[nested] nested object under path [" + nestedPath + "] is not of nested type"); + } + + // get our parent query which will determines our parent documents + Query parentQuery; + ObjectMapper objectMapper = context.nestedScope().getObjectMapper(); + if (objectMapper == null) { + parentQuery = Queries.newNonNestedFilter(); + } else { + parentQuery = objectMapper.nestedTypeFilter(); + } + + // get our child query, potentially applying a users filter + Query childQuery; + try { + context.nestedScope().nextLevel(nestedObjectMapper); + if (nestedFilter != null) { + assert nestedFilter == Rewriteable.rewrite(nestedFilter, context) : "nested filter is not rewritten"; + if (nested == null) { + // this is for back-compat, original single level nested sorting never applied a nested type filter + childQuery = nestedFilter.toFilter(context); + } else { + childQuery = Queries.filtered(nestedObjectMapper.nestedTypeFilter(), nestedFilter.toFilter(context)); + } + } else { + childQuery = nestedObjectMapper.nestedTypeFilter(); + } + } finally { + context.nestedScope().previousLevel(); + } + + // apply filters from the previous nested level + if (nested != null) { + parentQuery = Queries.filtered(parentQuery, + new ToParentBlockJoinQuery(nested.getInnerQuery(), nested.getRootFilter(), ScoreMode.None)); + + if (objectMapper != null) { + childQuery = Queries.filtered(childQuery, + new ToChildBlockJoinQuery(nested.getInnerQuery(), context.bitsetFilter(objectMapper.nestedTypeFilter()))); + } + } + + // wrap up our parent and child and either process the next level of nesting or return + final Nested innerNested = new Nested(context.bitsetFilter(parentQuery), childQuery); + if (nestedNestedSort != null) { + try { + context.nestedScope().nextLevel(nestedObjectMapper); + return resolveNested(context, nestedNestedSort, innerNested); + } finally { + context.nestedScope().previousLevel(); + } + } else { + return innerNested; } - return nested; } protected static QueryBuilder parseNestedFilter(XContentParser parser) { diff --git a/core/src/main/java/org/elasticsearch/search/sort/SortBuilders.java b/core/src/main/java/org/elasticsearch/search/sort/SortBuilders.java index 3eae9b8d019..9172d6b7bb7 100644 --- a/core/src/main/java/org/elasticsearch/search/sort/SortBuilders.java +++ b/core/src/main/java/org/elasticsearch/search/sort/SortBuilders.java @@ -56,6 +56,14 @@ public class SortBuilders { return new ScriptSortBuilder(script, type); } + /** + * Constructs a new nested sort builder. + * + */ + public static NestedSortBuilder nestedSort(String path) { + return new NestedSortBuilder(path); + } + /** * A geo distance based sort. * diff --git a/core/src/test/java/org/elasticsearch/search/nested/SimpleNestedIT.java b/core/src/test/java/org/elasticsearch/search/nested/SimpleNestedIT.java index 3e4792690ad..6fa298118eb 100644 --- a/core/src/test/java/org/elasticsearch/search/nested/SimpleNestedIT.java +++ b/core/src/test/java/org/elasticsearch/search/nested/SimpleNestedIT.java @@ -33,6 +33,7 @@ import org.elasticsearch.cluster.health.ClusterHealthStatus; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentFactory; +import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.search.sort.SortBuilders; import org.elasticsearch.search.sort.SortMode; @@ -498,6 +499,220 @@ public class SimpleNestedIT extends ESIntegTestCase { client().prepareClearScroll().addScrollId("_all").get(); } + public void testNestedSortWithMultiLevelFiltering() throws Exception { + assertAcked(prepareCreate("test") + .addMapping("type1", "{\n" + + " \"type1\": {\n" + + " \"properties\": {\n" + + " \"acl\": {\n" + + " \"type\": \"nested\",\n" + + " \"properties\": {\n" + + " \"access_id\": {\"type\": \"keyword\"},\n" + + " \"operation\": {\n" + + " \"type\": \"nested\",\n" + + " \"properties\": {\n" + + " \"name\": {\"type\": \"keyword\"},\n" + + " \"user\": {\n" + + " \"type\": \"nested\",\n" + + " \"properties\": {\n" + + " \"username\": {\"type\": \"keyword\"},\n" + + " \"id\": {\"type\": \"integer\"}\n" + + " }\n" + + " }\n" + + " }\n" + + " }\n" + + " }\n" + + " }\n" + + " }\n" + + " }\n" + + "}", XContentType.JSON)); + ensureGreen(); + + client().prepareIndex("test", "type1", "1").setSource("{\n" + + " \"acl\": [\n" + + " {\n" + + " \"access_id\": 1,\n" + + " \"operation\": [\n" + + " {\n" + + " \"name\": \"read\",\n" + + " \"user\": [\n" + + " {\"username\": \"matt\", \"id\": 1},\n" + + " {\"username\": \"shay\", \"id\": 2},\n" + + " {\"username\": \"adrien\", \"id\": 3}\n" + + " ]\n" + + " },\n" + + " {\n" + + " \"name\": \"write\",\n" + + " \"user\": [\n" + + " {\"username\": \"shay\", \"id\": 2},\n" + + " {\"username\": \"adrien\", \"id\": 3}\n" + + " ]\n" + + " }\n" + + " ]\n" + + " },\n" + + " {\n" + + " \"access_id\": 2,\n" + + " \"operation\": [\n" + + " {\n" + + " \"name\": \"read\",\n" + + " \"user\": [\n" + + " {\"username\": \"jim\", \"id\": 4},\n" + + " {\"username\": \"shay\", \"id\": 2}\n" + + " ]\n" + + " },\n" + + " {\n" + + " \"name\": \"write\",\n" + + " \"user\": [\n" + + " {\"username\": \"shay\", \"id\": 2}\n" + + " ]\n" + + " },\n" + + " {\n" + + " \"name\": \"execute\",\n" + + " \"user\": [\n" + + " {\"username\": \"shay\", \"id\": 2}\n" + + " ]\n" + + " }\n" + + " ]\n" + + " }\n" + + " ]\n" + + "}", XContentType.JSON).execute().actionGet(); + + client().prepareIndex("test", "type1", "2").setSource("{\n" + + " \"acl\": [\n" + + " {\n" + + " \"access_id\": 1,\n" + + " \"operation\": [\n" + + " {\n" + + " \"name\": \"read\",\n" + + " \"user\": [\n" + + " {\"username\": \"matt\", \"id\": 1},\n" + + " {\"username\": \"luca\", \"id\": 5}\n" + + " ]\n" + + " },\n" + + " {\n" + + " \"name\": \"execute\",\n" + + " \"user\": [\n" + + " {\"username\": \"luca\", \"id\": 5}\n" + + " ]\n" + + " }\n" + + " ]\n" + + " },\n" + + " {\n" + + " \"access_id\": 3,\n" + + " \"operation\": [\n" + + " {\n" + + " \"name\": \"read\",\n" + + " \"user\": [\n" + + " {\"username\": \"matt\", \"id\": 1}\n" + + " ]\n" + + " },\n" + + " {\n" + + " \"name\": \"write\",\n" + + " \"user\": [\n" + + " {\"username\": \"matt\", \"id\": 1}\n" + + " ]\n" + + " },\n" + + " {\n" + + " \"name\": \"execute\",\n" + + " \"user\": [\n" + + " {\"username\": \"matt\", \"id\": 1}\n" + + " ]\n" + + " }\n" + + " ]\n" + + " }\n" + + " ]\n" + + "}", XContentType.JSON).execute().actionGet(); + refresh(); + + // access id = 1, read, max value, asc, should use matt and shay + SearchResponse searchResponse = client().prepareSearch() + .setQuery(matchAllQuery()) + .addSort( + SortBuilders.fieldSort("acl.operation.user.username") + .setNestedSort(SortBuilders.nestedSort("acl") + .setFilter(QueryBuilders.termQuery("acl.access_id", "1")) + .setNestedSort(SortBuilders.nestedSort("acl.operation") + .setFilter(QueryBuilders.termQuery("acl.operation.name", "read")) + .setNestedSort(SortBuilders.nestedSort("acl.operation.user")))) + .sortMode(SortMode.MAX) + .order(SortOrder.ASC) + ) + .execute().actionGet(); + + assertHitCount(searchResponse, 2); + assertThat(searchResponse.getHits().getHits().length, equalTo(2)); + assertThat(searchResponse.getHits().getHits()[0].getId(), equalTo("2")); + assertThat(searchResponse.getHits().getHits()[0].getSortValues()[0].toString(), equalTo("matt")); + assertThat(searchResponse.getHits().getHits()[1].getId(), equalTo("1")); + assertThat(searchResponse.getHits().getHits()[1].getSortValues()[0].toString(), equalTo("shay")); + + + // access id = 1, read, min value, asc, should now use adrien and luca + searchResponse = client().prepareSearch() + .setQuery(matchAllQuery()) + .addSort( + SortBuilders.fieldSort("acl.operation.user.username") + .setNestedSort(SortBuilders.nestedSort("acl") + .setFilter(QueryBuilders.termQuery("acl.access_id", "1")) + .setNestedSort(SortBuilders.nestedSort("acl.operation") + .setFilter(QueryBuilders.termQuery("acl.operation.name", "read")) + .setNestedSort(SortBuilders.nestedSort("acl.operation.user")))) + .sortMode(SortMode.MIN) + .order(SortOrder.ASC) + ) + .execute().actionGet(); + + assertHitCount(searchResponse, 2); + assertThat(searchResponse.getHits().getHits().length, equalTo(2)); + assertThat(searchResponse.getHits().getHits()[0].getId(), equalTo("1")); + assertThat(searchResponse.getHits().getHits()[0].getSortValues()[0].toString(), equalTo("adrien")); + assertThat(searchResponse.getHits().getHits()[1].getId(), equalTo("2")); + assertThat(searchResponse.getHits().getHits()[1].getSortValues()[0].toString(), equalTo("luca")); + + // execute, by matt or luca, by user id, sort missing first + searchResponse = client().prepareSearch() + .setQuery(matchAllQuery()) + .addSort( + SortBuilders.fieldSort("acl.operation.user.id") + .setNestedSort(SortBuilders.nestedSort("acl") + .setNestedSort(SortBuilders.nestedSort("acl.operation") + .setFilter(QueryBuilders.termQuery("acl.operation.name", "execute")) + .setNestedSort(SortBuilders.nestedSort("acl.operation.user") + .setFilter(QueryBuilders.termsQuery("acl.operation.user.username", "matt", "luca"))))) + .missing("_first") + .sortMode(SortMode.MIN) + .order(SortOrder.DESC) + ) + .execute().actionGet(); + + assertHitCount(searchResponse, 2); + assertThat(searchResponse.getHits().getHits().length, equalTo(2)); + assertThat(searchResponse.getHits().getHits()[0].getId(), equalTo("1")); // missing first + assertThat(searchResponse.getHits().getHits()[1].getId(), equalTo("2")); + assertThat(searchResponse.getHits().getHits()[1].getSortValues()[0].toString(), equalTo("1")); + + // execute, by matt or luca, by username, sort missing last (default) + searchResponse = client().prepareSearch() + .setQuery(matchAllQuery()) + .addSort( + SortBuilders.fieldSort("acl.operation.user.username") + .setNestedSort(SortBuilders.nestedSort("acl") + .setNestedSort(SortBuilders.nestedSort("acl.operation") + .setFilter(QueryBuilders.termQuery("acl.operation.name", "execute")) + .setNestedSort(SortBuilders.nestedSort("acl.operation.user") + .setFilter(QueryBuilders.termsQuery("acl.operation.user.username", "matt", "luca"))))) + .sortMode(SortMode.MIN) + .order(SortOrder.DESC) + ) + .execute().actionGet(); + + assertHitCount(searchResponse, 2); + assertThat(searchResponse.getHits().getHits().length, equalTo(2)); + assertThat(searchResponse.getHits().getHits()[0].getId(), equalTo("2")); + assertThat(searchResponse.getHits().getHits()[0].getSortValues()[0].toString(), equalTo("luca")); + assertThat(searchResponse.getHits().getHits()[1].getId(), equalTo("1")); // missing last + } + public void testSortNestedWithNestedFilter() throws Exception { assertAcked(prepareCreate("test") .addMapping("type1", XContentFactory.jsonBuilder() @@ -529,7 +744,7 @@ public class SimpleNestedIT extends ESIntegTestCase { ensureGreen(); // sum: 11 - client().prepareIndex("test", "type1", Integer.toString(1)).setSource(jsonBuilder() + client().prepareIndex("test", "type1", "1").setSource(jsonBuilder() .startObject() .field("grand_parent_values", 1L) .startArray("parent") @@ -568,7 +783,7 @@ public class SimpleNestedIT extends ESIntegTestCase { .endObject()).execute().actionGet(); // sum: 7 - client().prepareIndex("test", "type1", Integer.toString(2)).setSource(jsonBuilder() + client().prepareIndex("test", "type1", "2").setSource(jsonBuilder() .startObject() .field("grand_parent_values", 2L) .startArray("parent") @@ -607,7 +822,7 @@ public class SimpleNestedIT extends ESIntegTestCase { .endObject()).execute().actionGet(); // sum: 2 - client().prepareIndex("test", "type1", Integer.toString(3)).setSource(jsonBuilder() + client().prepareIndex("test", "type1", "3").setSource(jsonBuilder() .startObject() .field("grand_parent_values", 3L) .startArray("parent") @@ -722,25 +937,27 @@ public class SimpleNestedIT extends ESIntegTestCase { assertThat(searchResponse.getHits().getHits()[2].getId(), equalTo("3")); assertThat(searchResponse.getHits().getHits()[2].getSortValues()[0].toString(), equalTo("3")); + searchResponse = client().prepareSearch() .setQuery(matchAllQuery()) .addSort( SortBuilders.fieldSort("parent.child.child_values") - .setNestedPath("parent.child") - .setNestedFilter(QueryBuilders.termQuery("parent.filter", false)) + .setNestedSort(SortBuilders.nestedSort("parent") + .setFilter(QueryBuilders.termQuery("parent.filter", false)) + .setNestedSort(SortBuilders.nestedSort("parent.child"))) + .sortMode(SortMode.MAX) .order(SortOrder.ASC) ) .execute().actionGet(); assertHitCount(searchResponse, 3); assertThat(searchResponse.getHits().getHits().length, equalTo(3)); - // TODO: If we expose ToChildBlockJoinQuery we can filter sort values based on a higher level nested objects -// assertThat(searchResponse.getHits().getHits()[0].getId(), equalTo("3")); -// assertThat(searchResponse.getHits().getHits()[0].sortValues()[0].toString(), equalTo("-3")); -// assertThat(searchResponse.getHits().getHits()[1].getId(), equalTo("2")); -// assertThat(searchResponse.getHits().getHits()[1].sortValues()[0].toString(), equalTo("-2")); -// assertThat(searchResponse.getHits().getHits()[2].getId(), equalTo("1")); -// assertThat(searchResponse.getHits().getHits()[2].sortValues()[0].toString(), equalTo("-1")); + assertThat(searchResponse.getHits().getHits()[0].getId(), equalTo("3")); + assertThat(searchResponse.getHits().getHits()[0].getSortValues()[0].toString(), equalTo("3")); + assertThat(searchResponse.getHits().getHits()[1].getId(), equalTo("2")); + assertThat(searchResponse.getHits().getHits()[1].getSortValues()[0].toString(), equalTo("4")); + assertThat(searchResponse.getHits().getHits()[2].getId(), equalTo("1")); + assertThat(searchResponse.getHits().getHits()[2].getSortValues()[0].toString(), equalTo("6")); // Check if closest nested type is resolved searchResponse = client().prepareSearch() diff --git a/core/src/test/java/org/elasticsearch/search/sort/FieldSortBuilderTests.java b/core/src/test/java/org/elasticsearch/search/sort/FieldSortBuilderTests.java index eda187d916f..cac373e7d79 100644 --- a/core/src/test/java/org/elasticsearch/search/sort/FieldSortBuilderTests.java +++ b/core/src/test/java/org/elasticsearch/search/sort/FieldSortBuilderTests.java @@ -62,11 +62,11 @@ public class FieldSortBuilderTests extends AbstractSortTestCase randomAlphaOfLengthBetween(1, 10))); + case 0: { + NestedSortBuilder nestedSort = new NestedSortBuilder(mutated.getNestedSort()); + nestedSort.setPath(randomValueOtherThan(nestedSort.getPath(), () -> randomAlphaOfLengthBetween(1, 10))); + mutated.setNestedSort(nestedSort); break; - case 1: - mutated.setNestedFilter(randomValueOtherThan( - original.getNestedFilter(), - () -> randomNestedFilter())); + } + case 1: { + NestedSortBuilder nestedSort = new NestedSortBuilder(mutated.getNestedSort()); + nestedSort.setFilter(randomValueOtherThan(nestedSort.getFilter(), AbstractSortTestCase::randomNestedFilter)); + mutated.setNestedSort(nestedSort); break; + } case 2: mutated.sortMode(randomValueOtherThan(original.sortMode(), () -> randomFrom(SortMode.values()))); break; diff --git a/core/src/test/java/org/elasticsearch/search/sort/GeoDistanceSortBuilderTests.java b/core/src/test/java/org/elasticsearch/search/sort/GeoDistanceSortBuilderTests.java index 007695886c3..7a0c91ba324 100644 --- a/core/src/test/java/org/elasticsearch/search/sort/GeoDistanceSortBuilderTests.java +++ b/core/src/test/java/org/elasticsearch/search/sort/GeoDistanceSortBuilderTests.java @@ -87,13 +87,16 @@ public class GeoDistanceSortBuilderTests extends AbstractSortTestCase randomFrom(SortMode.values()))); } if (randomBoolean()) { - result.setNestedFilter(new MatchAllQueryBuilder()); - } - if (randomBoolean()) { - result.setNestedPath( - randomValueOtherThan( - result.getNestedPath(), - () -> randomAlphaOfLengthBetween(1, 10))); + NestedSortBuilder nestedSort = SortBuilders.nestedSort( + randomValueOtherThan( + result.getNestedPath(), + () -> randomAlphaOfLengthBetween(1, 10))); + + if (randomBoolean()) { + nestedSort.setFilter(new MatchAllQueryBuilder()); + } + + result.setNestedSort(nestedSort); } if (randomBoolean()) { result.validation(randomValueOtherThan(result.validation(), () -> randomFrom(GeoValidationMethod.values()))); @@ -157,16 +160,18 @@ public class GeoDistanceSortBuilderTests extends AbstractSortTestCase randomFrom(SortMode.values()))); break; - case 6: - result.setNestedFilter(randomValueOtherThan( - original.getNestedFilter(), - () -> randomNestedFilter())); + case 6: { + NestedSortBuilder nestedSort = new NestedSortBuilder(result.getNestedSort()); + nestedSort.setFilter(randomValueOtherThan(nestedSort.getFilter(), AbstractSortTestCase::randomNestedFilter)); + result.setNestedSort(nestedSort); break; - case 7: - result.setNestedPath(randomValueOtherThan( - result.getNestedPath(), - () -> randomAlphaOfLengthBetween(1, 10))); + } + case 7: { + NestedSortBuilder nestedSort = new NestedSortBuilder(result.getNestedSort()); + nestedSort.setPath(randomValueOtherThan(nestedSort.getPath(), () -> randomAlphaOfLengthBetween(1, 10))); + result.setNestedSort(nestedSort); break; + } case 8: result.validation(randomValueOtherThan(result.validation(), () -> randomFrom(GeoValidationMethod.values()))); break; @@ -217,11 +222,13 @@ public class GeoDistanceSortBuilderTests extends AbstractSortTestCase randomNestedFilter())); + case 2: { + NestedSortBuilder nestedSort = new NestedSortBuilder(original.getNestedSort()); + nestedSort.setFilter(randomValueOtherThan(nestedSort.getFilter(), AbstractSortTestCase::randomNestedFilter)); + result.setNestedSort(nestedSort); break; - case 3: - result.setNestedPath(original.getNestedPath() + "_some_suffix"); + } + case 3: { + NestedSortBuilder nestedSort = new NestedSortBuilder(original.getNestedSort()); + nestedSort.setPath(nestedSort.getPath() + "_some_suffix"); + result.setNestedSort(nestedSort); break; + } } return result; } @@ -178,8 +183,7 @@ public class ScriptSortBuilderTests extends AbstractSortTestCase