Multi-level Nested Sort with Filters (#26395)
Multi-level Nested Sort with Filters Allow multiple levels of nested sorting where each level can have it's own filter. Backward compatible with previous single-level nested sort.
This commit is contained in:
parent
42e8940a3d
commit
140395c83f
|
@ -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<FD extends AtomicFieldData> extends IndexCompone
|
|||
this.innerQuery = innerQuery;
|
||||
}
|
||||
|
||||
public Query getInnerQuery() {
|
||||
return innerQuery;
|
||||
}
|
||||
|
||||
public BitSetProducer getRootFilter() {
|
||||
return rootFilter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a {@link BitDocIdSet} that matches the root documents.
|
||||
*/
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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<FieldSortBuilder> {
|
||||
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<FieldSortBuilder> {
|
|||
|
||||
private String nestedPath;
|
||||
|
||||
private NestedSortBuilder nestedSort;
|
||||
|
||||
/** Copy constructor. */
|
||||
public FieldSortBuilder(FieldSortBuilder template) {
|
||||
this(template.fieldName);
|
||||
|
@ -82,6 +91,7 @@ public class FieldSortBuilder extends SortBuilder<FieldSortBuilder> {
|
|||
}
|
||||
this.setNestedFilter(template.getNestedFilter());
|
||||
this.setNestedPath(template.getNestedPath());
|
||||
this.setNestedSort(template.getNestedSort());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -108,6 +118,9 @@ public class FieldSortBuilder extends SortBuilder<FieldSortBuilder> {
|
|||
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<FieldSortBuilder> {
|
|||
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<FieldSortBuilder> {
|
|||
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<FieldSortBuilder> {
|
|||
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<FieldSortBuilder> {
|
|||
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<FieldSortBuilder> {
|
|||
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<FieldSortBuilder> {
|
|||
|
||||
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
|
||||
|
|
|
@ -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<GeoDistanceSortBuilder> {
|
||||
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<GeoDistanceSortBuilder>
|
|||
private QueryBuilder nestedFilter;
|
||||
private String nestedPath;
|
||||
|
||||
private NestedSortBuilder nestedSort;
|
||||
|
||||
private GeoValidationMethod validation = DEFAULT_VALIDATION;
|
||||
|
||||
/**
|
||||
|
@ -141,6 +150,7 @@ public class GeoDistanceSortBuilder extends SortBuilder<GeoDistanceSortBuilder>
|
|||
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<GeoDistanceSortBuilder>
|
|||
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<GeoDistanceSortBuilder>
|
|||
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<GeoDistanceSortBuilder>
|
|||
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<GeoDistanceSortBuilder>
|
|||
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<GeoDistanceSortBuilder>
|
|||
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<GeoDistanceSortBuilder>
|
|||
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<GeoDistanceSortBuilder>
|
|||
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<GeoDistanceSortBuilder>
|
|||
} 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<GeoDistanceSortBuilder>
|
|||
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<GeoDistanceSortBuilder>
|
|||
+ "] 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
|
||||
|
|
|
@ -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<NestedSortBuilder>, 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);
|
||||
}
|
||||
}
|
|
@ -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<ScriptSortBuilder> {
|
||||
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<ScriptSortBuilder> {
|
|||
|
||||
private String nestedPath;
|
||||
|
||||
private NestedSortBuilder nestedSort;
|
||||
|
||||
/**
|
||||
* Constructs a script sort builder with the given script.
|
||||
*
|
||||
|
@ -100,6 +107,7 @@ public class ScriptSortBuilder extends SortBuilder<ScriptSortBuilder> {
|
|||
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<ScriptSortBuilder> {
|
|||
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<ScriptSortBuilder> {
|
|||
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<ScriptSortBuilder> {
|
|||
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<ScriptSortBuilder> {
|
|||
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<ScriptSortBuilder> {
|
|||
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<ScriptSortBuilder> {
|
|||
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<ScriptSortBuilder> {
|
|||
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
|
||||
|
|
|
@ -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<T extends SortBuilder<T>> 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) {
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -62,11 +62,11 @@ public class FieldSortBuilderTests extends AbstractSortTestCase<FieldSortBuilder
|
|||
}
|
||||
|
||||
if (randomBoolean()) {
|
||||
builder.setNestedFilter(randomNestedFilter());
|
||||
}
|
||||
|
||||
if (randomBoolean()) {
|
||||
builder.setNestedPath(randomAlphaOfLengthBetween(1, 10));
|
||||
NestedSortBuilder nestedSort = SortBuilders.nestedSort(randomAlphaOfLengthBetween(1, 10));
|
||||
if (randomBoolean()) {
|
||||
nestedSort.setFilter(randomNestedFilter());
|
||||
}
|
||||
builder.setNestedSort(nestedSort);
|
||||
}
|
||||
|
||||
return builder;
|
||||
|
@ -77,16 +77,18 @@ public class FieldSortBuilderTests extends AbstractSortTestCase<FieldSortBuilder
|
|||
FieldSortBuilder mutated = new FieldSortBuilder(original);
|
||||
int parameter = randomIntBetween(0, 5);
|
||||
switch (parameter) {
|
||||
case 0:
|
||||
mutated.setNestedPath(randomValueOtherThan(
|
||||
original.getNestedPath(),
|
||||
() -> 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;
|
||||
|
|
|
@ -87,13 +87,16 @@ public class GeoDistanceSortBuilderTests extends AbstractSortTestCase<GeoDistanc
|
|||
result.sortMode(randomValueOtherThan(SortMode.SUM, () -> 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<GeoDistanc
|
|||
Arrays.asList(SortMode.SUM, result.sortMode())::contains,
|
||||
() -> 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<GeoDistanc
|
|||
" \"unit\" : \"m\",\n" +
|
||||
" \"distance_type\" : \"arc\",\n" +
|
||||
" \"mode\" : \"MAX\",\n" +
|
||||
" \"nested_filter\" : {\n" +
|
||||
" \"ids\" : {\n" +
|
||||
" \"type\" : [ ],\n" +
|
||||
" \"values\" : [ ],\n" +
|
||||
" \"boost\" : 5.711116\n" +
|
||||
" \"nested\" : {\n" +
|
||||
" \"filter\" : {\n" +
|
||||
" \"ids\" : {\n" +
|
||||
" \"type\" : [ ],\n" +
|
||||
" \"values\" : [ ],\n" +
|
||||
" \"boost\" : 5.711116\n" +
|
||||
" }\n" +
|
||||
" }\n" +
|
||||
" },\n" +
|
||||
" \"validation_method\" : \"STRICT\"\n" +
|
||||
|
|
|
@ -59,10 +59,12 @@ public class ScriptSortBuilderTests extends AbstractSortTestCase<ScriptSortBuild
|
|||
}
|
||||
}
|
||||
if (randomBoolean()) {
|
||||
builder.setNestedFilter(randomNestedFilter());
|
||||
}
|
||||
if (randomBoolean()) {
|
||||
builder.setNestedPath(randomAlphaOfLengthBetween(1, 10));
|
||||
NestedSortBuilder nestedSort = SortBuilders.nestedSort(randomAlphaOfLengthBetween(1, 10));
|
||||
if (randomBoolean()) {
|
||||
nestedSort.setFilter(randomNestedFilter());
|
||||
}
|
||||
|
||||
builder.setNestedSort(nestedSort);
|
||||
}
|
||||
return builder;
|
||||
}
|
||||
|
@ -83,8 +85,7 @@ public class ScriptSortBuilderTests extends AbstractSortTestCase<ScriptSortBuild
|
|||
if (original.sortMode() != null && result.type() == ScriptSortType.NUMBER) {
|
||||
result.sortMode(original.sortMode());
|
||||
}
|
||||
result.setNestedFilter(original.getNestedFilter());
|
||||
result.setNestedPath(original.getNestedPath());
|
||||
result.setNestedSort(original.getNestedSort());
|
||||
return result;
|
||||
}
|
||||
result = new ScriptSortBuilder(original);
|
||||
|
@ -108,14 +109,18 @@ public class ScriptSortBuilderTests extends AbstractSortTestCase<ScriptSortBuild
|
|||
}
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
result.setNestedFilter(randomValueOtherThan(
|
||||
original.getNestedFilter(),
|
||||
() -> 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<ScriptSortBuild
|
|||
assertEquals(ScriptSortType.NUMBER, builder.type());
|
||||
assertEquals(SortOrder.ASC, builder.order());
|
||||
assertEquals(SortMode.MAX, builder.sortMode());
|
||||
assertNull(builder.getNestedFilter());
|
||||
assertNull(builder.getNestedPath());
|
||||
assertNull(builder.getNestedSort());
|
||||
}
|
||||
|
||||
public void testParseJson_simple() throws IOException {
|
||||
|
@ -203,8 +207,7 @@ public class ScriptSortBuilderTests extends AbstractSortTestCase<ScriptSortBuild
|
|||
assertEquals(ScriptSortType.NUMBER, builder.type());
|
||||
assertEquals(SortOrder.ASC, builder.order());
|
||||
assertEquals(SortMode.MAX, builder.sortMode());
|
||||
assertNull(builder.getNestedFilter());
|
||||
assertNull(builder.getNestedPath());
|
||||
assertNull(builder.getNestedSort());
|
||||
}
|
||||
|
||||
public void testParseBadFieldNameExceptions() throws IOException {
|
||||
|
|
|
@ -115,24 +115,35 @@ POST /_search
|
|||
|
||||
Elasticsearch also supports sorting by
|
||||
fields that are inside one or more nested objects. The sorting by nested
|
||||
field support has the following parameters on top of the already
|
||||
existing sort options:
|
||||
field support has a `nested` sort option with the following properties:
|
||||
|
||||
`nested_path`::
|
||||
`path`::
|
||||
Defines on which nested object to sort. The actual
|
||||
sort field must be a direct field inside this nested object.
|
||||
When sorting by nested field, this field is mandatory.
|
||||
|
||||
`nested_filter`::
|
||||
`filter`::
|
||||
A filter that the inner objects inside the nested path
|
||||
should match with in order for its field values to be taken into account
|
||||
by sorting. Common case is to repeat the query / filter inside the
|
||||
nested filter or query. By default no `nested_filter` is active.
|
||||
`nested`::
|
||||
Same as top-level `nested` but applies to another nested path within the
|
||||
current nested object.
|
||||
|
||||
===== Nested sorting example
|
||||
[WARNING]
|
||||
.Nested sort options before Elasticseach 6.1
|
||||
============================================
|
||||
|
||||
The `nested_path` and `nested_filter` options have been deprecated in
|
||||
favor of the options documented above.
|
||||
|
||||
============================================
|
||||
|
||||
===== Nested sorting examples
|
||||
|
||||
In the below example `offer` is a field of type `nested`.
|
||||
The `nested_path` needs to be specified; otherwise, elasticsearch doesn't know on what nested level sort values need to be captured.
|
||||
The nested `path` needs to be specified; otherwise, elasticsearch doesn't know on what nested level sort values need to be captured.
|
||||
|
||||
[source,js]
|
||||
--------------------------------------------------
|
||||
|
@ -146,9 +157,11 @@ POST /_search
|
|||
"offer.price" : {
|
||||
"mode" : "avg",
|
||||
"order" : "asc",
|
||||
"nested_path" : "offer",
|
||||
"nested_filter" : {
|
||||
"term" : { "offer.color" : "blue" }
|
||||
"nested": {
|
||||
"path": "offer",
|
||||
"filter": {
|
||||
"term" : { "offer.color" : "blue" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -157,6 +170,53 @@ POST /_search
|
|||
--------------------------------------------------
|
||||
// CONSOLE
|
||||
|
||||
In the below example `parent` and `child` fields are of type `nested`.
|
||||
The `nested_path` needs to be specified at each level; otherwise, elasticsearch doesn't know on what nested level sort values need to be captured.
|
||||
|
||||
[source,js]
|
||||
--------------------------------------------------
|
||||
POST /_search
|
||||
{
|
||||
"query": {
|
||||
"nested": {
|
||||
"path": "parent",
|
||||
"query": {
|
||||
"bool": {
|
||||
"must": {"range": {"parent.age": {"gte": 21}}},
|
||||
"filter": {
|
||||
"nested": {
|
||||
"path": "parent.child",
|
||||
"query": {"match": {"parent.child.name": "matt"}}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"sort" : [
|
||||
{
|
||||
"parent.child.age" : {
|
||||
"mode" : "min",
|
||||
"order" : "asc",
|
||||
"nested": {
|
||||
"path": "parent",
|
||||
"filter": {
|
||||
"range": {"parent.age": {"gte": 21}}
|
||||
},
|
||||
"nested": {
|
||||
"path": "parent.child",
|
||||
"filter": {
|
||||
"match": {"parent.child.name": "matt"}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
--------------------------------------------------
|
||||
// CONSOLE
|
||||
|
||||
Nested sorting is also supported when sorting by
|
||||
scripts and sorting by geo distance.
|
||||
|
||||
|
|
Loading…
Reference in New Issue