Add parsing of list of sort builders to SortBuilder

Moving the current parsing code for the whole "sort" element
in the seach source over to static "fromXContent" method in
SortBuilder.
This commit is contained in:
Christoph Büscher 2016-03-22 14:28:29 +01:00
parent 3ed4ff054f
commit 64d362ab9d
10 changed files with 430 additions and 46 deletions

View File

@ -62,6 +62,7 @@ import java.util.Objects;
*/
public class GeoDistanceSortBuilder extends SortBuilder<GeoDistanceSortBuilder> implements SortBuilderParser<GeoDistanceSortBuilder> {
public static final String NAME = "_geo_distance";
public static final String ALTERNATIVE_NAME = "_geoDistance";
public static final boolean DEFAULT_COERCE = false;
public static final boolean DEFAULT_IGNORE_MALFORMED = false;
public static final ParseField UNIT_FIELD = new ParseField("unit");

View File

@ -38,7 +38,7 @@ import java.util.Objects;
*/
public class ScoreSortBuilder extends SortBuilder<ScoreSortBuilder> implements SortBuilderParser<ScoreSortBuilder> {
private static final String NAME = "_score";
public 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");
@ -88,6 +88,7 @@ public class ScoreSortBuilder extends SortBuilder<ScoreSortBuilder> implements S
return result;
}
@Override
public SortField build(QueryShardContext context) {
if (order == SortOrder.DESC) {
return SORT_SCORE;

View File

@ -66,7 +66,7 @@ import java.util.Objects;
*/
public class ScriptSortBuilder extends SortBuilder<ScriptSortBuilder> implements SortBuilderParser<ScriptSortBuilder> {
private static final String NAME = "_script";
public static final String NAME = "_script";
static final ScriptSortBuilder PROTOTYPE = new ScriptSortBuilder(new Script("_na_"), ScriptSortType.STRING);
public static final ParseField TYPE_FIELD = new ParseField("type");
public static final ParseField SCRIPT_FIELD = new ParseField("script");

View File

@ -20,6 +20,8 @@
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.elasticsearch.ElasticsearchException;
import org.elasticsearch.common.ParseField;
@ -27,47 +29,44 @@ import org.elasticsearch.common.lucene.search.Queries;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.index.fielddata.IndexFieldData.XFieldComparatorSource.Nested;
import org.elasticsearch.index.mapper.object.ObjectMapper;
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.internal.SearchContext;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import static java.util.Collections.unmodifiableMap;
/**
*
*/
public abstract class SortBuilder<T extends SortBuilder<?>> implements ToXContent {
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);
innerDocumentsQuery = QueryBuilder.rewriteQuery(nestedFilter, context).toFilter(context);
context.nestedScope().previousLevel();
} else {
innerDocumentsQuery = nestedObjectMapper.nestedTypeFilter();
}
nested = new Nested(rootDocumentsFilter, innerDocumentsQuery);
}
return nested;
}
public abstract class SortBuilder<T extends SortBuilder<?>> implements SortBuilderParser<T>, ToXContent {
protected SortOrder order = SortOrder.ASC;
public static final ParseField ORDER_FIELD = new ParseField("order");
private static final Map<String, SortBuilder<?>> PARSERS;
static {
Map<String, SortBuilder<?>> parsers = new HashMap<>();
parsers.put(ScriptSortBuilder.NAME, ScriptSortBuilder.PROTOTYPE);
parsers.put(GeoDistanceSortBuilder.NAME, new GeoDistanceSortBuilder("_na_", -1, -1));
parsers.put(GeoDistanceSortBuilder.ALTERNATIVE_NAME, new GeoDistanceSortBuilder("_na_", -1, -1));
parsers.put(ScoreSortBuilder.NAME, ScoreSortBuilder.PROTOTYPE);
PARSERS = unmodifiableMap(parsers);
}
@Override
public String toString() {
try {
@ -96,4 +95,122 @@ public abstract class SortBuilder<T extends SortBuilder<?>> implements ToXConten
public SortOrder order() {
return this.order;
}
public static List<SortBuilder<?>> fromXContent(QueryParseContext context) throws IOException {
List<SortBuilder<?>> sortFields = new ArrayList<>(2);
XContentParser parser = context.parser();
XContentParser.Token token = parser.currentToken();
if (token == XContentParser.Token.START_ARRAY) {
while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) {
if (token == XContentParser.Token.START_OBJECT) {
parseCompoundSortField(parser, context, sortFields);
} else if (token == XContentParser.Token.VALUE_STRING) {
String fieldName = parser.text();
if (fieldName.equals(ScoreSortBuilder.NAME)) {
sortFields.add(new ScoreSortBuilder());
} else {
sortFields.add(new FieldSortBuilder(fieldName));
}
} else {
throw new IllegalArgumentException("malformed sort format, "
+ "within the sort array, an object, or an actual string are allowed");
}
}
} else if (token == XContentParser.Token.VALUE_STRING) {
String fieldName = parser.text();
if (fieldName.equals(ScoreSortBuilder.NAME)) {
sortFields.add(new ScoreSortBuilder());
} else {
sortFields.add(new FieldSortBuilder(fieldName));
}
} else if (token == XContentParser.Token.START_OBJECT) {
parseCompoundSortField(parser, context, sortFields);
} else {
throw new IllegalArgumentException("malformed sort format, either start with array, object, or an actual string");
}
return sortFields;
}
private static void parseCompoundSortField(XContentParser parser, QueryParseContext context, List<SortBuilder<?>> sortFields)
throws IOException {
XContentParser.Token token;
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == XContentParser.Token.FIELD_NAME) {
String fieldName = parser.currentName();
token = parser.nextToken();
if (token == XContentParser.Token.VALUE_STRING) {
SortOrder order = SortOrder.fromString(parser.text());
if (fieldName.equals(ScoreSortBuilder.NAME)) {
sortFields.add(new ScoreSortBuilder().order(order));
} else {
sortFields.add(new FieldSortBuilder(fieldName).order(order));
}
} else {
if (PARSERS.containsKey(fieldName)) {
sortFields.add(PARSERS.get(fieldName).fromXContent(context, fieldName));
} else {
sortFields.add(FieldSortBuilder.PROTOTYPE.fromXContent(context, fieldName));
}
}
}
}
}
public static void parseSort(XContentParser parser, SearchContext context) throws IOException {
QueryParseContext parseContext = context.getQueryShardContext().parseContext();
parseContext.reset(parser);
Optional<Sort> sortOptional = buildSort(SortBuilder.fromXContent(parseContext), context.getQueryShardContext());
if (sortOptional.isPresent()) {
context.sort(sortOptional.get());
}
}
public static Optional<Sort> buildSort(List<SortBuilder<?>> sortBuilders, QueryShardContext context) throws IOException {
List<SortField> sortFields = new ArrayList<>(sortBuilders.size());
for (SortBuilder<?> builder : sortBuilders) {
sortFields.add(builder.build(context));
}
if (!sortFields.isEmpty()) {
// optimize if we just sort on score non reversed, we don't really need sorting
boolean sort;
if (sortFields.size() > 1) {
sort = true;
} else {
SortField sortField = sortFields.get(0);
if (sortField.getType() == SortField.Type.SCORE && !sortField.getReverse()) {
sort = false;
} else {
sort = true;
}
}
if (sort) {
return Optional.of(new Sort(sortFields.toArray(new SortField[sortFields.size()])));
}
}
return Optional.empty();
}
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);
innerDocumentsQuery = QueryBuilder.rewriteQuery(nestedFilter, context).toFilter(context);
context.nestedScope().previousLevel();
} else {
innerDocumentsQuery = nestedObjectMapper.nestedTypeFilter();
}
nested = new Nested(rootDocumentsFilter, innerDocumentsQuery);
}
return nested;
}
}

View File

@ -25,10 +25,14 @@ public class FieldSortBuilderTests extends AbstractSortTestCase<FieldSortBuilder
@Override
protected FieldSortBuilder createTestItem() {
return randomFieldSortBuilder();
}
public static FieldSortBuilder randomFieldSortBuilder() {
String fieldName = rarely() ? SortParseElement.DOC_FIELD_NAME : randomAsciiOfLengthBetween(1, 10);
FieldSortBuilder builder = new FieldSortBuilder(fieldName);
if (randomBoolean()) {
builder.order(RandomSortDataGenerator.order(builder.order()));
builder.order(RandomSortDataGenerator.order(null));
}
if (randomBoolean()) {

View File

@ -38,6 +38,10 @@ public class GeoDistanceSortBuilderTests extends AbstractSortTestCase<GeoDistanc
@Override
protected GeoDistanceSortBuilder createTestItem() {
return randomGeoDistanceSortBuilder();
}
public static GeoDistanceSortBuilder randomGeoDistanceSortBuilder() {
String fieldName = randomAsciiOfLengthBetween(1, 10);
GeoDistanceSortBuilder result = null;
@ -70,7 +74,7 @@ public class GeoDistanceSortBuilderTests extends AbstractSortTestCase<GeoDistanc
result.unit(unit(result.unit()));
}
if (randomBoolean()) {
result.order(RandomSortDataGenerator.order(result.order()));
result.order(RandomSortDataGenerator.order(null));
}
if (randomBoolean()) {
result.sortMode(mode(result.sortMode()));

View File

@ -19,7 +19,7 @@
package org.elasticsearch.search.sort;
import org.apache.lucene.util.BytesRef;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.index.query.IdsQueryBuilder;
import org.elasticsearch.index.query.MatchAllQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
@ -79,15 +79,10 @@ public class RandomSortDataGenerator {
public static Object missing(Object original) {
Object missing = null;
Object otherMissing = null;
if (original instanceof BytesRef) {
otherMissing = ((BytesRef) original).utf8ToString();
} else {
otherMissing = original;
}
Object otherMissing = original;
while (missing == null || missing.equals(otherMissing)) {
int missingId = ESTestCase.randomIntBetween(0, 3);
int missingId = ESTestCase.randomIntBetween(0, 4);
switch (missingId) {
case 0:
missing = ("_last");
@ -99,6 +94,9 @@ public class RandomSortDataGenerator {
missing = ESTestCase.randomAsciiOfLength(10);
break;
case 3:
missing = ESTestCase.randomUnicodeOfCodepointLengthBetween(5, 15);
break;
case 4:
missing = ESTestCase.randomInt();
break;
default:
@ -109,9 +107,14 @@ public class RandomSortDataGenerator {
return missing;
}
public static SortOrder order(SortOrder original) {
SortOrder order = SortOrder.ASC;
if (order.equals(original)) {
/**
* return a random {@link SortOrder} settings, except the one provided by parameter if set
*/
public static SortOrder order(@Nullable SortOrder original) {
if (original == null) {
return ESTestCase.randomBoolean() ? SortOrder.ASC : SortOrder.DESC;
}
if (original.equals(SortOrder.ASC)) {
return SortOrder.DESC;
} else {
return SortOrder.ASC;

View File

@ -34,17 +34,17 @@ public class ScoreSortBuilderTests extends AbstractSortTestCase<ScoreSortBuilder
@Override
protected ScoreSortBuilder createTestItem() {
return randomScoreSortBuilder();
}
public static ScoreSortBuilder randomScoreSortBuilder() {
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);
}
result.order(RandomSortDataGenerator.order(original.order()));
return result;
}

View File

@ -40,11 +40,15 @@ public class ScriptSortBuilderTests extends AbstractSortTestCase<ScriptSortBuild
@Override
protected ScriptSortBuilder createTestItem() {
return randomScriptSortBuilder();
}
public static ScriptSortBuilder randomScriptSortBuilder() {
ScriptSortType type = randomBoolean() ? ScriptSortType.NUMBER : ScriptSortType.STRING;
ScriptSortBuilder builder = new ScriptSortBuilder(new Script(randomAsciiOfLengthBetween(5, 10)),
type);
if (randomBoolean()) {
builder.order(RandomSortDataGenerator.order(builder.order()));
builder.order(RandomSortDataGenerator.order(null));
}
if (randomBoolean()) {
if (type == ScriptSortType.NUMBER) {

View File

@ -0,0 +1,250 @@
/*
x * 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.bytes.BytesArray;
import org.elasticsearch.common.geo.GeoPoint;
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentHelper;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.index.query.QueryParseContext;
import org.elasticsearch.indices.query.IndicesQueriesRegistry;
import org.elasticsearch.search.SearchModule;
import org.elasticsearch.test.ESTestCase;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class SortBuilderTests extends ESTestCase {
private static final int NUMBER_OF_RUNS = 20;
protected static NamedWriteableRegistry namedWriteableRegistry;
static IndicesQueriesRegistry indicesQueriesRegistry;
SortParseElement parseElement = new SortParseElement();
@BeforeClass
public static void init() {
namedWriteableRegistry = new NamedWriteableRegistry();
namedWriteableRegistry.registerPrototype(SortBuilder.class, GeoDistanceSortBuilder.PROTOTYPE);
namedWriteableRegistry.registerPrototype(SortBuilder.class, ScoreSortBuilder.PROTOTYPE);
namedWriteableRegistry.registerPrototype(SortBuilder.class, ScriptSortBuilder.PROTOTYPE);
namedWriteableRegistry.registerPrototype(SortBuilder.class, FieldSortBuilder.PROTOTYPE);
indicesQueriesRegistry = new SearchModule(Settings.EMPTY, namedWriteableRegistry).buildQueryParserRegistry();
}
@AfterClass
public static void afterClass() throws Exception {
namedWriteableRegistry = null;
}
/**
* test two syntax variations:
* - "sort" : "fieldname"
* - "sort" : { "fieldname" : "asc" }
*/
public void testSingleFieldSort() throws IOException {
SortOrder order = randomBoolean() ? SortOrder.ASC : SortOrder.DESC;
String json = "{ \"sort\" : { \"field1\" : \"" + order + "\" }}";
List<SortBuilder<?>> result = parseSort(json);
assertEquals(1, result.size());
SortBuilder<?> sortBuilder = result.get(0);
assertEquals(new FieldSortBuilder("field1").order(order), sortBuilder);
json = "{ \"sort\" : \"field1\" }";
result = parseSort(json);
assertEquals(1, result.size());
sortBuilder = result.get(0);
assertEquals(new FieldSortBuilder("field1"), sortBuilder);
json = "{ \"sort\" : { \"_doc\" : \"" + order + "\" }}";
result = parseSort(json);
assertEquals(1, result.size());
sortBuilder = result.get(0);
assertEquals(new FieldSortBuilder("_doc").order(order), sortBuilder);
json = "{ \"sort\" : \"_doc\" }";
result = parseSort(json);
assertEquals(1, result.size());
sortBuilder = result.get(0);
assertEquals(new FieldSortBuilder("_doc"), sortBuilder);
json = "{ \"sort\" : { \"_score\" : \"" + order +"\" }}";
result = parseSort(json);
assertEquals(1, result.size());
sortBuilder = result.get(0);
assertEquals(new ScoreSortBuilder().order(order), sortBuilder);
json = "{ \"sort\" : \"_score\" }";
result = parseSort(json);
assertEquals(1, result.size());
sortBuilder = result.get(0);
assertEquals(new ScoreSortBuilder(), sortBuilder);
// test two spellings for _geo_disctance
json = "{ \"sort\" : ["
+ "{\"_geoDistance\" : {"
+ "\"pin.location\" : \"40,-70\" } }"
+ "] }";
result = parseSort(json);
assertEquals(1, result.size());
sortBuilder = result.get(0);
assertEquals(new GeoDistanceSortBuilder("pin.location", 40, -70), sortBuilder);
json = "{ \"sort\" : ["
+ "{\"_geo_distance\" : {"
+ "\"pin.location\" : \"40,-70\" } }"
+ "] }";
result = parseSort(json);
assertEquals(1, result.size());
sortBuilder = result.get(0);
assertEquals(new GeoDistanceSortBuilder("pin.location", 40, -70), sortBuilder);
}
/**
* test random syntax variations
*/
public void testRandomSortBuilders() throws IOException {
for (int runs = 0; runs < NUMBER_OF_RUNS; runs++) {
List<SortBuilder<?>> testBuilders = randomSortBuilderList();
XContentBuilder xContentBuilder = XContentFactory.jsonBuilder();
xContentBuilder.startObject();
if (testBuilders.size() > 1) {
xContentBuilder.startArray("sort");
} else {
xContentBuilder.field("sort");
}
for (SortBuilder<?> builder : testBuilders) {
if (builder instanceof ScoreSortBuilder || builder instanceof FieldSortBuilder) {
switch (randomIntBetween(0, 2)) {
case 0:
if (builder instanceof ScoreSortBuilder) {
xContentBuilder.value("_score");
} else {
xContentBuilder.value(((FieldSortBuilder) builder).getFieldName());
}
break;
case 1:
xContentBuilder.startObject();
if (builder instanceof ScoreSortBuilder) {
xContentBuilder.field("_score");
} else {
xContentBuilder.field(((FieldSortBuilder) builder).getFieldName());
}
xContentBuilder.value(builder.order());
xContentBuilder.endObject();
break;
case 2:
xContentBuilder.startObject();
builder.toXContent(xContentBuilder, ToXContent.EMPTY_PARAMS);
xContentBuilder.endObject();
break;
}
} else {
xContentBuilder.startObject();
builder.toXContent(xContentBuilder, ToXContent.EMPTY_PARAMS);
xContentBuilder.endObject();
}
}
if (testBuilders.size() > 1) {
xContentBuilder.endArray();
}
xContentBuilder.endObject();
List<SortBuilder<?>> parsedSort = parseSort(xContentBuilder.string());
assertEquals(testBuilders.size(), parsedSort.size());
Iterator<SortBuilder<?>> iterator = testBuilders.iterator();
for (SortBuilder<?> parsedBuilder : parsedSort) {
assertEquals(iterator.next(), parsedBuilder);
}
}
}
public static List<SortBuilder<?>> randomSortBuilderList() {
int size = randomIntBetween(1, 5);
List<SortBuilder<?>> list = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
switch (randomIntBetween(0, 3)) {
case 0:
list.add(new ScoreSortBuilder());
break;
case 1:
String fieldName = rarely() ? SortParseElement.DOC_FIELD_NAME : randomAsciiOfLengthBetween(1, 10);
list.add(new FieldSortBuilder(fieldName));
break;
case 2:
list.add(GeoDistanceSortBuilderTests.randomGeoDistanceSortBuilder());
break;
case 3:
list.add(ScriptSortBuilderTests.randomScriptSortBuilder());
break;
default:
throw new IllegalStateException("unexpected randomization in tests");
}
}
return list;
}
/**
* test array syntax variations:
* - "sort" : [ "fieldname", { "fieldname2" : "asc" }, ...]
*/
public void testMultiFieldSort() throws IOException {
String json = "{ \"sort\" : ["
+ "{ \"post_date\" : {\"order\" : \"asc\"}},"
+ "\"user\","
+ "{ \"name\" : \"desc\" },"
+ "{ \"age\" : \"desc\" },"
+ "{"
+ "\"_geo_distance\" : {"
+ "\"pin.location\" : \"40,-70\" } },"
+ "\"_score\""
+ "] }";
List<SortBuilder<?>> result = parseSort(json);
assertEquals(6, result.size());
assertEquals(new FieldSortBuilder("post_date").order(SortOrder.ASC), result.get(0));
assertEquals(new FieldSortBuilder("user").order(SortOrder.ASC), result.get(1));
assertEquals(new FieldSortBuilder("name").order(SortOrder.DESC), result.get(2));
assertEquals(new FieldSortBuilder("age").order(SortOrder.DESC), result.get(3));
assertEquals(new GeoDistanceSortBuilder("pin.location", new GeoPoint(40, -70)), result.get(4));
assertEquals(new ScoreSortBuilder(), result.get(5));
}
private static List<SortBuilder<?>> parseSort(String jsonString) throws IOException {
XContentParser itemParser = XContentHelper.createParser(new BytesArray(jsonString));
QueryParseContext context = new QueryParseContext(indicesQueriesRegistry);
context.reset(itemParser);
assertEquals(XContentParser.Token.START_OBJECT, itemParser.nextToken());
assertEquals(XContentParser.Token.FIELD_NAME, itemParser.nextToken());
assertEquals("sort", itemParser.currentName());
itemParser.nextToken();
return SortBuilder.fromXContent(context);
}
}