-
Notifications
You must be signed in to change notification settings - Fork 25k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add build() method to SortBuilder implementations #17146
Changes from 1 commit
1908d6d
d9de129
4c3d889
25da6b2
697174d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,6 +19,7 @@ | |
|
||
package org.elasticsearch.search.sort; | ||
|
||
import org.apache.lucene.search.SortField; | ||
import org.apache.lucene.util.BytesRef; | ||
import org.elasticsearch.common.ParseField; | ||
import org.elasticsearch.common.ParsingException; | ||
|
@@ -27,8 +28,14 @@ | |
import org.elasticsearch.common.lucene.BytesRefs; | ||
import org.elasticsearch.common.xcontent.XContentBuilder; | ||
import org.elasticsearch.common.xcontent.XContentParser; | ||
import org.elasticsearch.index.fielddata.IndexFieldData; | ||
import org.elasticsearch.index.fielddata.IndexFieldData.XFieldComparatorSource.Nested; | ||
import org.elasticsearch.index.mapper.MappedFieldType; | ||
import org.elasticsearch.index.query.QueryBuilder; | ||
import org.elasticsearch.index.query.QueryParseContext; | ||
import org.elasticsearch.index.query.QueryShardContext; | ||
import org.elasticsearch.index.query.QueryShardException; | ||
import org.elasticsearch.search.MultiValueMode; | ||
|
||
import java.io.IOException; | ||
import java.util.Objects; | ||
|
@@ -47,6 +54,13 @@ public class FieldSortBuilder extends SortBuilder<FieldSortBuilder> implements S | |
public static final ParseField SORT_MODE = new ParseField("mode"); | ||
public static final ParseField UNMAPPED_TYPE = new ParseField("unmapped_type"); | ||
|
||
/** | ||
* special field name to sort by index order | ||
*/ | ||
public static final String DOC_FIELD_NAME = "_doc"; | ||
private static final SortField SORT_DOC = new SortField(null, SortField.Type.DOC); | ||
private static final SortField SORT_DOC_REVERSE = new SortField(null, SortField.Type.DOC, true); | ||
|
||
private final String fieldName; | ||
|
||
private Object missing; | ||
|
@@ -161,7 +175,7 @@ public SortMode sortMode() { | |
* TODO should the above getters and setters be deprecated/ changed in | ||
* favour of real getters and setters? | ||
*/ | ||
public FieldSortBuilder setNestedFilter(QueryBuilder nestedFilter) { | ||
public FieldSortBuilder setNestedFilter(QueryBuilder<?> nestedFilter) { | ||
this.nestedFilter = nestedFilter; | ||
return this; | ||
} | ||
|
@@ -170,7 +184,7 @@ public FieldSortBuilder setNestedFilter(QueryBuilder nestedFilter) { | |
* Returns the nested filter that the nested objects should match with in | ||
* order to be taken into account for sorting. | ||
*/ | ||
public QueryBuilder getNestedFilter() { | ||
public QueryBuilder<?> getNestedFilter() { | ||
return this.nestedFilter; | ||
} | ||
|
||
|
@@ -219,6 +233,56 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws | |
return builder; | ||
} | ||
|
||
@Override | ||
public SortField build(QueryShardContext context) throws IOException { | ||
if (DOC_FIELD_NAME.equals(fieldName)) { | ||
if (order == SortOrder.DESC) { | ||
return SORT_DOC_REVERSE; | ||
} else { | ||
return SORT_DOC; | ||
} | ||
} else { | ||
MappedFieldType fieldType = context.fieldMapper(fieldName); | ||
if (fieldType == null) { | ||
if (unmappedType != null) { | ||
fieldType = context.getMapperService().unmappedFieldType(unmappedType); | ||
} else { | ||
throw new QueryShardException(context, "No mapping found for [" + fieldName + "] in order to sort on"); | ||
} | ||
} | ||
|
||
if (!fieldType.isSortable()) { | ||
throw new QueryShardException(context, "Sorting not supported for field[" + fieldName + "]"); | ||
} | ||
|
||
// Enable when we also know how to detect fields that do tokenize, but only emit one token | ||
/*if (fieldMapper instanceof StringFieldMapper) { | ||
StringFieldMapper stringFieldMapper = (StringFieldMapper) fieldMapper; | ||
if (stringFieldMapper.fieldType().tokenized()) { | ||
// Fail early | ||
throw new SearchParseException(context, "Can't sort on tokenized string field[" + fieldName + "]"); | ||
} | ||
}*/ | ||
|
||
MultiValueMode localSortMode = null; | ||
if (sortMode != null) { | ||
localSortMode = MultiValueMode.fromString(sortMode.toString()); | ||
} | ||
if (fieldType.isNumeric() == false && (sortMode == SortMode.SUM || sortMode == SortMode.AVG)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Lost one comment copying this over - intentional? <- after reading one line further, scratch the comment above. |
||
throw new QueryShardException(context, "we only support AVG and SUM on number based fields"); | ||
} | ||
boolean reverse = (order == SortOrder.DESC); | ||
if (localSortMode == null) { | ||
localSortMode = reverse ? MultiValueMode.MAX : MultiValueMode.MIN; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. By directly using order here we could delete the variable "reverse" above. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmpf - should first read through and comment afterwards. I saw only now that we need "reverse" further below anyway :/ |
||
} | ||
|
||
final Nested nested = resolveNested(context, nestedPath, nestedFilter); | ||
IndexFieldData.XFieldComparatorSource fieldComparatorSource = context.getForField(fieldType) | ||
.comparatorSource(missing, localSortMode, nested); | ||
return new SortField(fieldType.name(), fieldComparatorSource, reverse); | ||
} | ||
} | ||
|
||
@Override | ||
public boolean equals(Object other) { | ||
if (this == other) { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,17 +19,34 @@ | |
|
||
package org.elasticsearch.search.sort; | ||
|
||
import org.apache.lucene.index.LeafReaderContext; | ||
import org.apache.lucene.index.NumericDocValues; | ||
import org.apache.lucene.search.DocIdSetIterator; | ||
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.geo.GeoDistance; | ||
import org.elasticsearch.common.geo.GeoDistance.FixedSourceDistance; | ||
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.unit.DistanceUnit; | ||
import org.elasticsearch.common.xcontent.XContentBuilder; | ||
import org.elasticsearch.common.xcontent.XContentParser; | ||
import org.elasticsearch.index.fielddata.IndexFieldData; | ||
import org.elasticsearch.index.fielddata.IndexFieldData.XFieldComparatorSource.Nested; | ||
import org.elasticsearch.index.fielddata.IndexGeoPointFieldData; | ||
import org.elasticsearch.index.fielddata.MultiGeoPointValues; | ||
import org.elasticsearch.index.fielddata.NumericDoubleValues; | ||
import org.elasticsearch.index.fielddata.SortedNumericDoubleValues; | ||
import org.elasticsearch.index.mapper.MappedFieldType; | ||
import org.elasticsearch.index.query.QueryBuilder; | ||
import org.elasticsearch.index.query.QueryParseContext; | ||
import org.elasticsearch.index.query.QueryShardContext; | ||
import org.elasticsearch.search.MultiValueMode; | ||
|
||
import java.io.IOException; | ||
import java.util.ArrayList; | ||
|
@@ -405,13 +422,10 @@ public GeoDistanceSortBuilder fromXContent(QueryParseContext context, String ele | |
|
||
fieldName = currentName; | ||
} else if (token == XContentParser.Token.START_OBJECT) { | ||
// the json in the format of -> field : { lat : 30, lon : 12 } | ||
if ("nested_filter".equals(currentName) || "nestedFilter".equals(currentName)) { | ||
// TODO Note to remember: while this is kept as a QueryBuilder internally, | ||
// we need to make sure to call toFilter() on it once on the shard | ||
// (e.g. in the new build() method) | ||
nestedFilter = context.parseInnerQueryBuilder(); | ||
} else { | ||
// the json in the format of -> field : { lat : 30, lon : 12 } | ||
fieldName = currentName; | ||
GeoPoint point = new GeoPoint(); | ||
GeoUtils.parseGeoPoint(parser, point); | ||
|
@@ -461,7 +475,89 @@ public GeoDistanceSortBuilder fromXContent(QueryParseContext context, String ele | |
result.coerce(coerce); | ||
result.ignoreMalformed(ignoreMalformed); | ||
return result; | ||
} | ||
|
||
@Override | ||
public SortField build(QueryShardContext context) throws IOException { | ||
final boolean indexCreatedBeforeV2_0 = context.indexVersionCreated().before(Version.V_2_0_0); | ||
// validation was not available prior to 2.x, so to support bwc percolation queries we only ignore_malformed on 2.x created indexes | ||
List<GeoPoint> localPoints = new ArrayList<GeoPoint>(); | ||
for (GeoPoint geoPoint : this.points) { | ||
localPoints.add(new GeoPoint(geoPoint)); | ||
} | ||
|
||
if (!indexCreatedBeforeV2_0 && !ignoreMalformed) { | ||
for (GeoPoint point : localPoints) { | ||
if (point.lat() > 90.0 || point.lat() < -90.0) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not use this: GeoUtils.isValidLatitude() There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. sure |
||
throw new ElasticsearchParseException("illegal latitude value [{}] for [GeoDistanceSort]", point.lat()); | ||
} | ||
if (point.lon() > 180.0 || point.lon() < -180) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. See above, there's another method for longitude validation. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
throw new ElasticsearchParseException("illegal longitude value [{}] for [GeoDistanceSort]", point.lon()); | ||
} | ||
} | ||
} | ||
|
||
if (coerce) { | ||
for (GeoPoint point : localPoints) { | ||
GeoUtils.normalizePoint(point, coerce, coerce); | ||
} | ||
} | ||
|
||
boolean reverse = (order == SortOrder.DESC); | ||
final MultiValueMode finalSortMode; | ||
if (sortMode == null) { | ||
finalSortMode = reverse ? MultiValueMode.MAX : MultiValueMode.MIN; | ||
} else { | ||
finalSortMode = MultiValueMode.fromString(sortMode.toString()); | ||
} | ||
|
||
if (sortMode == SortMode.SUM) { | ||
throw new IllegalArgumentException("sort_mode [sum] isn't supported for sorting by geo distance"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. https://www.elastic.co/guide/en/elasticsearch/reference/current/search-request-sort.html ... says that "geo distance sorting supports sort_mode options: min, max and avg. Looking here: https://github.com/elastic/elasticsearch/blob/7ccf7adbdf748e3dc2cb37c027bcf30a1bae54a9/core/src/main/java/org/elasticsearch/search/sort/SortMode.java we have min, max, avg, sum and median We forbid sum above - but what about median? Bug in the docs? Bug in the original implementation? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Two things here, need to do some more digging:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. MultiValueMode.MEDIAN seems to work with GeoDistanceSort, I added a test to GeoDistanceSortBuilderIT. The check for correct SortMode in build() can be removed because we already check this in the setter and there should be no other way to set the SortMode. |
||
} | ||
|
||
MappedFieldType fieldType = context.fieldMapper(fieldName); | ||
if (fieldType == null) { | ||
throw new IllegalArgumentException("failed to find mapper for [" + fieldName + "] for geo distance based sort"); | ||
} | ||
final IndexGeoPointFieldData geoIndexFieldData = context.getForField(fieldType); | ||
final FixedSourceDistance[] distances = new FixedSourceDistance[localPoints.size()]; | ||
for (int i = 0; i< localPoints.size(); i++) { | ||
distances[i] = geoDistance.fixedSourceDistance(localPoints.get(i).lat(), localPoints.get(i).lon(), unit); | ||
} | ||
|
||
final Nested nested = resolveNested(context, nestedPath, nestedFilter); | ||
|
||
IndexFieldData.XFieldComparatorSource geoDistanceComparatorSource = new IndexFieldData.XFieldComparatorSource() { | ||
|
||
@Override | ||
public SortField.Type reducedType() { | ||
return SortField.Type.DOUBLE; | ||
} | ||
|
||
@Override | ||
public FieldComparator<?> newComparator(String fieldname, int numHits, int sortPos, boolean reversed) throws IOException { | ||
return new FieldComparator.DoubleComparator(numHits, null, null) { | ||
@Override | ||
protected NumericDocValues getNumericDocValues(LeafReaderContext context, String field) throws IOException { | ||
final MultiGeoPointValues geoPointValues = geoIndexFieldData.load(context).getGeoPointValues(); | ||
final SortedNumericDoubleValues distanceValues = GeoDistance.distanceValues(geoPointValues, distances); | ||
final NumericDoubleValues selectedValues; | ||
if (nested == null) { | ||
selectedValues = finalSortMode.select(distanceValues, Double.MAX_VALUE); | ||
} else { | ||
final BitSet rootDocs = nested.rootDocs(context); | ||
final DocIdSetIterator innerDocs = nested.innerDocs(context); | ||
selectedValues = finalSortMode.select(distanceValues, Double.MAX_VALUE, rootDocs, innerDocs, | ||
context.reader().maxDoc()); | ||
} | ||
return selectedValues.getRawDoubleValues(); | ||
} | ||
}; | ||
} | ||
|
||
}; | ||
|
||
return new SortField(fieldName, geoDistanceComparatorSource, reverse); | ||
} | ||
|
||
static void parseGeoPoints(XContentParser parser, List<GeoPoint> geoPoints) throws IOException { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of having code commented out here - would it be better to track that in some follow up issue? (I know the comment was there before the refactoring, but code that's commented out always smells strange IMHO)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just talked to @martijnvg, this is old code and can be removed.