Sort: Make ScoreSortBuilder implement NamedWriteable and add fromXContent parsing
This change makes ScoreSortBuilder implement NamedWriteable, adds equals() and hashCode() and also implements parsing ScoreSortBuilder back from xContent. This is needed for the ongoing Search refactoring.
This commit is contained in:
parent
7a53a396e4
commit
11b18a9963
|
@ -19,35 +19,116 @@
|
|||
|
||||
package org.elasticsearch.search.sort;
|
||||
|
||||
import org.elasticsearch.common.ParseField;
|
||||
import org.elasticsearch.common.ParseFieldMatcher;
|
||||
import org.elasticsearch.common.ParsingException;
|
||||
import org.elasticsearch.common.io.stream.NamedWriteable;
|
||||
import org.elasticsearch.common.io.stream.StreamInput;
|
||||
import org.elasticsearch.common.io.stream.StreamOutput;
|
||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.index.query.QueryParseContext;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* A sort builder allowing to sort by score.
|
||||
*
|
||||
*
|
||||
*/
|
||||
public class ScoreSortBuilder extends SortBuilder {
|
||||
public class ScoreSortBuilder extends SortBuilder implements NamedWriteable<ScoreSortBuilder>,
|
||||
SortElementParserTemp<ScoreSortBuilder> {
|
||||
|
||||
private SortOrder order;
|
||||
private static final String NAME = "_score";
|
||||
static final ScoreSortBuilder PROTOTYPE = new ScoreSortBuilder();
|
||||
public static final ParseField REVERSE_FIELD = new ParseField("reverse");
|
||||
public static final ParseField ORDER_FIELD = new ParseField("order");
|
||||
private SortOrder order = SortOrder.DESC;
|
||||
|
||||
/**
|
||||
* The order of sort scoring. By default, its {@link SortOrder#DESC}.
|
||||
*/
|
||||
@Override
|
||||
public ScoreSortBuilder order(SortOrder order) {
|
||||
Objects.requireNonNull(order, "sort order cannot be null.");
|
||||
this.order = order;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the order of sort scoring. By default, its {@link SortOrder#DESC}.
|
||||
*/
|
||||
public SortOrder order() {
|
||||
return this.order;
|
||||
}
|
||||
|
||||
@Override
|
||||
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
|
||||
builder.startObject("_score");
|
||||
builder.startObject(NAME);
|
||||
if (order == SortOrder.ASC) {
|
||||
builder.field("reverse", true);
|
||||
builder.field(REVERSE_FIELD.getPreferredName(), true);
|
||||
}
|
||||
builder.endObject();
|
||||
return builder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScoreSortBuilder fromXContent(QueryParseContext context, String elementName) throws IOException {
|
||||
XContentParser parser = context.parser();
|
||||
ParseFieldMatcher matcher = context.parseFieldMatcher();
|
||||
|
||||
XContentParser.Token token;
|
||||
String currentName = parser.currentName();
|
||||
ScoreSortBuilder result = new ScoreSortBuilder();
|
||||
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
|
||||
if (token == XContentParser.Token.FIELD_NAME) {
|
||||
currentName = parser.currentName();
|
||||
} else if (token.isValue()) {
|
||||
if (matcher.match(currentName, REVERSE_FIELD)) {
|
||||
if (parser.booleanValue()) {
|
||||
result.order(SortOrder.ASC);
|
||||
}
|
||||
// else we keep the default DESC
|
||||
} else if (matcher.match(currentName, ORDER_FIELD)) {
|
||||
result.order(SortOrder.fromString(parser.text()));
|
||||
} else {
|
||||
throw new ParsingException(parser.getTokenLocation(), "[" + NAME + "] failed to parse field [" + currentName + "]");
|
||||
}
|
||||
} else {
|
||||
throw new ParsingException(parser.getTokenLocation(), "[" + NAME + "] unexpected token [" + token + "]");
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object object) {
|
||||
if (this == object) {
|
||||
return true;
|
||||
}
|
||||
if (object == null || getClass() != object.getClass()) {
|
||||
return false;
|
||||
}
|
||||
ScoreSortBuilder other = (ScoreSortBuilder) object;
|
||||
return Objects.equals(order, other.order);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(this.order);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTo(StreamOutput out) throws IOException {
|
||||
order.writeTo(out);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScoreSortBuilder readFrom(StreamInput in) throws IOException {
|
||||
return new ScoreSortBuilder().order(SortOrder.readOrderFrom(in));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getWriteableName() {
|
||||
return NAME;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,13 +19,12 @@
|
|||
|
||||
package org.elasticsearch.search.sort;
|
||||
|
||||
import org.elasticsearch.common.xcontent.ToXContent;
|
||||
import org.elasticsearch.index.query.QueryParseContext;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
// TODO once sort refactoring is done this needs to be merged into SortBuilder
|
||||
public interface SortElementParserTemp<T extends ToXContent> {
|
||||
public interface SortElementParserTemp<T extends SortBuilder> {
|
||||
/**
|
||||
* Creates a new SortBuilder from the json held by the {@link SortElementParserTemp}
|
||||
* in {@link org.elasticsearch.common.xcontent.XContent} format
|
||||
|
|
|
@ -43,7 +43,7 @@ import java.io.IOException;
|
|||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
|
||||
public abstract class AbstractSortTestCase<T extends NamedWriteable<T> & ToXContent & SortElementParserTemp<T>> extends ESTestCase {
|
||||
public abstract class AbstractSortTestCase<T extends SortBuilder & NamedWriteable<T> & SortElementParserTemp<T>> extends ESTestCase {
|
||||
|
||||
protected static NamedWriteableRegistry namedWriteableRegistry;
|
||||
|
||||
|
@ -53,7 +53,8 @@ public abstract class AbstractSortTestCase<T extends NamedWriteable<T> & ToXCont
|
|||
@BeforeClass
|
||||
public static void init() {
|
||||
namedWriteableRegistry = new NamedWriteableRegistry();
|
||||
namedWriteableRegistry.registerPrototype(GeoDistanceSortBuilder.class, GeoDistanceSortBuilder.PROTOTYPE);
|
||||
namedWriteableRegistry.registerPrototype(SortBuilder.class, GeoDistanceSortBuilder.PROTOTYPE);
|
||||
namedWriteableRegistry.registerPrototype(SortBuilder.class, ScoreSortBuilder.PROTOTYPE);
|
||||
indicesQueriesRegistry = new SearchModule(Settings.EMPTY, namedWriteableRegistry).buildQueryParserRegistry();
|
||||
}
|
||||
|
||||
|
@ -85,9 +86,9 @@ public abstract class AbstractSortTestCase<T extends NamedWriteable<T> & ToXCont
|
|||
|
||||
XContentParser itemParser = XContentHelper.createParser(builder.bytes());
|
||||
itemParser.nextToken();
|
||||
|
||||
|
||||
/*
|
||||
* filter out name of sort, or field name to sort on for element fieldSort
|
||||
* filter out name of sort, or field name to sort on for element fieldSort
|
||||
*/
|
||||
itemParser.nextToken();
|
||||
String elementName = itemParser.currentName();
|
||||
|
@ -95,7 +96,7 @@ public abstract class AbstractSortTestCase<T extends NamedWriteable<T> & ToXCont
|
|||
|
||||
QueryParseContext context = new QueryParseContext(indicesQueriesRegistry);
|
||||
context.reset(itemParser);
|
||||
NamedWriteable<T> parsedItem = testItem.fromXContent(context, elementName);
|
||||
SortBuilder parsedItem = testItem.fromXContent(context, elementName);
|
||||
assertNotSame(testItem, parsedItem);
|
||||
assertEquals(testItem, parsedItem);
|
||||
assertEquals(testItem.hashCode(), parsedItem.hashCode());
|
||||
|
@ -146,17 +147,15 @@ public abstract class AbstractSortTestCase<T extends NamedWriteable<T> & ToXCont
|
|||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected T copyItem(T original) throws IOException {
|
||||
try (BytesStreamOutput output = new BytesStreamOutput()) {
|
||||
original.writeTo(output);
|
||||
try (StreamInput in = new NamedWriteableAwareStreamInput(StreamInput.wrap(output.bytes()), namedWriteableRegistry)) {
|
||||
@SuppressWarnings("unchecked")
|
||||
T prototype = (T) namedWriteableRegistry.getPrototype(getPrototype(), original.getWriteableName());
|
||||
T copy = (T) prototype.readFrom(in);
|
||||
return copy;
|
||||
T prototype = (T) namedWriteableRegistry.getPrototype(SortBuilder.class,
|
||||
original.getWriteableName());
|
||||
return prototype.readFrom(in);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract Class<T> getPrototype();
|
||||
}
|
||||
|
|
|
@ -60,7 +60,7 @@ public class GeoDistanceSortBuilderTests extends AbstractSortTestCase<GeoDistanc
|
|||
break;
|
||||
default:
|
||||
throw new IllegalStateException("one of three geo initialisation strategies must be used");
|
||||
|
||||
|
||||
}
|
||||
if (randomBoolean()) {
|
||||
result.geoDistance(geoDistance(result.geoDistance()));
|
||||
|
@ -86,7 +86,7 @@ public class GeoDistanceSortBuilderTests extends AbstractSortTestCase<GeoDistanc
|
|||
if (randomBoolean()) {
|
||||
result.ignoreMalformed(! result.ignoreMalformed());
|
||||
}
|
||||
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -172,12 +172,6 @@ public class GeoDistanceSortBuilderTests extends AbstractSortTestCase<GeoDistanc
|
|||
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
protected Class<GeoDistanceSortBuilder> getPrototype() {
|
||||
return (Class<GeoDistanceSortBuilder>) GeoDistanceSortBuilder.PROTOTYPE.getClass();
|
||||
}
|
||||
|
||||
public void testSortModeSumIsRejectedInSetter() {
|
||||
GeoDistanceSortBuilder builder = new GeoDistanceSortBuilder("testname", -1, -1);
|
||||
GeoPoint point = RandomGeoGenerator.randomPoint(getRandom());
|
||||
|
@ -189,23 +183,23 @@ public class GeoDistanceSortBuilderTests extends AbstractSortTestCase<GeoDistanc
|
|||
// all good
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void testSortModeSumIsRejectedInJSON() throws IOException {
|
||||
String json = "{\n" +
|
||||
" \"testname\" : [ {\n" +
|
||||
" \"lat\" : -6.046997540714173,\n" +
|
||||
" \"lon\" : -51.94128329747579\n" +
|
||||
" } ],\n" +
|
||||
" \"unit\" : \"m\",\n" +
|
||||
" \"distance_type\" : \"sloppy_arc\",\n" +
|
||||
" \"reverse\" : true,\n" +
|
||||
" \"mode\" : \"SUM\",\n" +
|
||||
" \"coerce\" : false,\n" +
|
||||
" \"ignore_malformed\" : false\n" +
|
||||
String json = "{\n" +
|
||||
" \"testname\" : [ {\n" +
|
||||
" \"lat\" : -6.046997540714173,\n" +
|
||||
" \"lon\" : -51.94128329747579\n" +
|
||||
" } ],\n" +
|
||||
" \"unit\" : \"m\",\n" +
|
||||
" \"distance_type\" : \"sloppy_arc\",\n" +
|
||||
" \"reverse\" : true,\n" +
|
||||
" \"mode\" : \"SUM\",\n" +
|
||||
" \"coerce\" : false,\n" +
|
||||
" \"ignore_malformed\" : false\n" +
|
||||
"}";
|
||||
XContentParser itemParser = XContentHelper.createParser(new BytesArray(json));
|
||||
itemParser.nextToken();
|
||||
|
||||
|
||||
QueryParseContext context = new QueryParseContext(indicesQueriesRegistry);
|
||||
context.reset(itemParser);
|
||||
|
||||
|
@ -216,28 +210,28 @@ public class GeoDistanceSortBuilderTests extends AbstractSortTestCase<GeoDistanc
|
|||
// all good
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void testGeoDistanceSortCanBeParsedFromGeoHash() throws IOException {
|
||||
String json = "{\n" +
|
||||
String json = "{\n" +
|
||||
" \"VDcvDuFjE\" : [ \"7umzzv8eychg\", \"dmdgmt5z13uw\", " +
|
||||
" \"ezu09wxw6v4c\", \"kc7s3515p6k6\", \"jgeuvjwrmfzn\", \"kcpcfj7ruyf8\" ],\n" +
|
||||
" \"unit\" : \"m\",\n" +
|
||||
" \"distance_type\" : \"sloppy_arc\",\n" +
|
||||
" \"reverse\" : true,\n" +
|
||||
" \"mode\" : \"MAX\",\n" +
|
||||
" \"nested_filter\" : {\n" +
|
||||
" \"ids\" : {\n" +
|
||||
" \"type\" : [ ],\n" +
|
||||
" \"values\" : [ ],\n" +
|
||||
" \"boost\" : 5.711116\n" +
|
||||
" }\n" +
|
||||
" },\n" +
|
||||
" \"coerce\" : false,\n" +
|
||||
" \"ignore_malformed\" : true\n" +
|
||||
" \"ezu09wxw6v4c\", \"kc7s3515p6k6\", \"jgeuvjwrmfzn\", \"kcpcfj7ruyf8\" ],\n" +
|
||||
" \"unit\" : \"m\",\n" +
|
||||
" \"distance_type\" : \"sloppy_arc\",\n" +
|
||||
" \"reverse\" : true,\n" +
|
||||
" \"mode\" : \"MAX\",\n" +
|
||||
" \"nested_filter\" : {\n" +
|
||||
" \"ids\" : {\n" +
|
||||
" \"type\" : [ ],\n" +
|
||||
" \"values\" : [ ],\n" +
|
||||
" \"boost\" : 5.711116\n" +
|
||||
" }\n" +
|
||||
" },\n" +
|
||||
" \"coerce\" : false,\n" +
|
||||
" \"ignore_malformed\" : true\n" +
|
||||
" }";
|
||||
XContentParser itemParser = XContentHelper.createParser(new BytesArray(json));
|
||||
itemParser.nextToken();
|
||||
|
||||
|
||||
QueryParseContext context = new QueryParseContext(indicesQueriesRegistry);
|
||||
context.reset(itemParser);
|
||||
|
||||
|
@ -247,6 +241,6 @@ public class GeoDistanceSortBuilderTests extends AbstractSortTestCase<GeoDistanc
|
|||
+ "43.71844606474042, -5.548660643398762, "
|
||||
+ "-37.20467280596495, 38.71751043945551, "
|
||||
+ "-69.44606635719538, 84.25200328230858, "
|
||||
+ "-39.03717711567879, 44.74099852144718]", Arrays.toString(result.points()));
|
||||
+ "-39.03717711567879, 44.74099852144718]", Arrays.toString(result.points()));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* 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 org.elasticsearch.common.ParseFieldMatcher;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.xcontent.XContentFactory;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.index.query.QueryParseContext;
|
||||
import org.junit.Rule;
|
||||
import org.junit.rules.ExpectedException;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class ScoreSortBuilderTests extends AbstractSortTestCase<ScoreSortBuilder> {
|
||||
|
||||
@Override
|
||||
protected ScoreSortBuilder createTestItem() {
|
||||
return new ScoreSortBuilder().order(randomBoolean() ? SortOrder.ASC : SortOrder.DESC);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ScoreSortBuilder mutate(ScoreSortBuilder original) throws IOException {
|
||||
ScoreSortBuilder result = new ScoreSortBuilder();
|
||||
if (original.order() == SortOrder.ASC) {
|
||||
result.order(SortOrder.DESC);
|
||||
} else {
|
||||
result.order(SortOrder.ASC);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Rule
|
||||
public ExpectedException exceptionRule = ExpectedException.none();
|
||||
|
||||
/**
|
||||
* test passing null to {@link ScoreSortBuilder#order(SortOrder)} is illegal
|
||||
*/
|
||||
public void testIllegalOrder() {
|
||||
exceptionRule.expect(NullPointerException.class);
|
||||
exceptionRule.expectMessage("sort order cannot be null.");
|
||||
new ScoreSortBuilder().order(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* test parsing order parameter if specified as `order` field in the json
|
||||
* instead of the `reverse` field that we render in toXContent
|
||||
*/
|
||||
public void testParseOrder() throws IOException {
|
||||
QueryParseContext context = new QueryParseContext(indicesQueriesRegistry);
|
||||
context.parseFieldMatcher(new ParseFieldMatcher(Settings.EMPTY));
|
||||
SortOrder order = randomBoolean() ? SortOrder.ASC : SortOrder.DESC;
|
||||
String scoreSortString = "{ \"_score\": { \"order\": \""+ order.toString() +"\" }}";
|
||||
XContentParser parser = XContentFactory.xContent(scoreSortString).createParser(scoreSortString);
|
||||
// need to skip until parser is located on second START_OBJECT
|
||||
parser.nextToken();
|
||||
parser.nextToken();
|
||||
parser.nextToken();
|
||||
|
||||
context.reset(parser);
|
||||
ScoreSortBuilder scoreSort = ScoreSortBuilder.PROTOTYPE.fromXContent(context, "_score");
|
||||
assertEquals(order, scoreSort.order());
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue