Extend testing of build method in ScriptSortBuilder (#26520)

Improve testing around the ScriptSortBuilder#build method, adding checks for
correct transfers of the sort mode and nested sorts.

Also changing the behaviour around the nested_path, nested_filter vs. nested
parameter in a similar way as in #26490 and deprecating the setters/getters for
the old syntax.

Closes #17286
This commit is contained in:
Christoph Büscher 2017-09-07 10:37:50 +02:00 committed by GitHub
parent c9964d17bf
commit 47ffa17efb
5 changed files with 148 additions and 6 deletions

View File

@ -408,7 +408,7 @@ public class FieldSortBuilder extends SortBuilder<FieldSortBuilder> {
} }
@Override @Override
public SortBuilder rewrite(QueryRewriteContext ctx) throws IOException { public FieldSortBuilder rewrite(QueryRewriteContext ctx) throws IOException {
if (nestedFilter == null) { if (nestedFilter == null) {
return this; return this;
} }

View File

@ -176,15 +176,24 @@ public class ScriptSortBuilder extends SortBuilder<ScriptSortBuilder> {
/** /**
* Sets the nested filter that the nested objects should match with in order to be taken into account * Sets the nested filter that the nested objects should match with in order to be taken into account
* for sorting. * for sorting.
*
* @deprecated set nested sort with {@link #setNestedSort(NestedSortBuilder)} and retrieve with {@link #getNestedSort()}
*/ */
@Deprecated
public ScriptSortBuilder setNestedFilter(QueryBuilder nestedFilter) { public ScriptSortBuilder setNestedFilter(QueryBuilder nestedFilter) {
if (this.nestedSort != null) {
throw new IllegalArgumentException("Setting both nested_path/nested_filter and nested not allowed");
}
this.nestedFilter = nestedFilter; this.nestedFilter = nestedFilter;
return this; return this;
} }
/** /**
* Gets the nested filter. * Gets the nested filter.
*
* @deprecated set nested sort with {@link #setNestedSort(NestedSortBuilder)} and retrieve with {@link #getNestedSort()}
*/ */
@Deprecated
public QueryBuilder getNestedFilter() { public QueryBuilder getNestedFilter() {
return this.nestedFilter; return this.nestedFilter;
} }
@ -192,24 +201,45 @@ public class ScriptSortBuilder extends SortBuilder<ScriptSortBuilder> {
/** /**
* Sets the nested path if sorting occurs on a field that is inside a nested object. For sorting by script this * Sets the nested path if sorting occurs on a field that is inside a nested object. For sorting by script this
* needs to be specified. * needs to be specified.
*
* @deprecated set nested sort with {@link #setNestedSort(NestedSortBuilder)} and retrieve with {@link #getNestedSort()}
*/ */
@Deprecated
public ScriptSortBuilder setNestedPath(String nestedPath) { public ScriptSortBuilder setNestedPath(String nestedPath) {
if (this.nestedSort != null) {
throw new IllegalArgumentException("Setting both nested_path/nested_filter and nested not allowed");
}
this.nestedPath = nestedPath; this.nestedPath = nestedPath;
return this; return this;
} }
/** /**
* Gets the nested path. * Gets the nested path.
*
* @deprecated set nested sort with {@link #setNestedSort(NestedSortBuilder)} and retrieve with {@link #getNestedSort()}
*/ */
@Deprecated
public String getNestedPath() { public String getNestedPath() {
return this.nestedPath; return this.nestedPath;
} }
/**
* Returns the {@link NestedSortBuilder}
*/
public NestedSortBuilder getNestedSort() { public NestedSortBuilder getNestedSort() {
return this.nestedSort; return this.nestedSort;
} }
/**
* Sets the {@link NestedSortBuilder} to be used for fields that are inside a nested
* object. The {@link NestedSortBuilder} takes a `path` argument and an optional
* nested filter that the nested objects should match with in
* order to be taken into account for sorting.
*/
public ScriptSortBuilder setNestedSort(final NestedSortBuilder nestedSort) { public ScriptSortBuilder setNestedSort(final NestedSortBuilder nestedSort) {
if (this.nestedFilter != null || this.nestedPath != null) {
throw new IllegalArgumentException("Setting both nested_path/nested_filter and nested not allowed");
}
this.nestedSort = nestedSort; this.nestedSort = nestedSort;
return this; return this;
} }
@ -420,7 +450,7 @@ public class ScriptSortBuilder extends SortBuilder<ScriptSortBuilder> {
} }
@Override @Override
public SortBuilder rewrite(QueryRewriteContext ctx) throws IOException { public ScriptSortBuilder rewrite(QueryRewriteContext ctx) throws IOException {
if (nestedFilter == null) { if (nestedFilter == null) {
return this; return this;
} }

View File

@ -76,13 +76,14 @@ public abstract class AbstractSortTestCase<T extends SortBuilder<T>> extends EST
private static NamedXContentRegistry xContentRegistry; private static NamedXContentRegistry xContentRegistry;
private static ScriptService scriptService; private static ScriptService scriptService;
protected static String MOCK_SCRIPT_NAME = "dummy";
@BeforeClass @BeforeClass
public static void init() { public static void init() {
Settings baseSettings = Settings.builder() Settings baseSettings = Settings.builder()
.put(Environment.PATH_HOME_SETTING.getKey(), createTempDir().toString()) .put(Environment.PATH_HOME_SETTING.getKey(), createTempDir().toString())
.build(); .build();
Map<String, Function<Map<String, Object>, Object>> scripts = Collections.singletonMap("dummy", p -> null); Map<String, Function<Map<String, Object>, Object>> scripts = Collections.singletonMap(MOCK_SCRIPT_NAME, p -> null);
ScriptEngine engine = new MockScriptEngine(MockScriptEngine.NAME, scripts); ScriptEngine engine = new MockScriptEngine(MockScriptEngine.NAME, scripts);
scriptService = new ScriptService(baseSettings, Collections.singletonMap(engine.getType(), engine), ScriptModule.CORE_CONTEXTS); scriptService = new ScriptService(baseSettings, Collections.singletonMap(engine.getType(), engine), ScriptModule.CORE_CONTEXTS);

View File

@ -365,7 +365,6 @@ public class FieldSortBuilderTests extends AbstractSortTestCase<FieldSortBuilder
iae = expectThrows(IllegalArgumentException.class, iae = expectThrows(IllegalArgumentException.class,
() -> sortBuilder.setNestedSort(new NestedSortBuilder("otherPath")).setNestedFilter(QueryBuilders.matchAllQuery())); () -> sortBuilder.setNestedSort(new NestedSortBuilder("otherPath")).setNestedFilter(QueryBuilders.matchAllQuery()));
assertEquals("Setting both nested_path/nested_filter and nested not allowed", iae.getMessage()); assertEquals("Setting both nested_path/nested_filter and nested not allowed", iae.getMessage());
} }
@Override @Override

View File

@ -20,12 +20,23 @@
package org.elasticsearch.search.sort; package org.elasticsearch.search.sort;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.MatchAllDocsQuery;
import org.apache.lucene.search.SortField; import org.apache.lucene.search.SortField;
import org.apache.lucene.search.TermQuery;
import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.json.JsonXContent; import org.elasticsearch.common.xcontent.json.JsonXContent;
import org.elasticsearch.index.fielddata.IndexFieldData.XFieldComparatorSource;
import org.elasticsearch.index.fielddata.IndexFieldData.XFieldComparatorSource.Nested;
import org.elasticsearch.index.fielddata.fieldcomparator.BytesRefFieldComparatorSource;
import org.elasticsearch.index.fielddata.fieldcomparator.DoubleValuesComparatorSource;
import org.elasticsearch.index.mapper.TypeFieldMapper;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.QueryShardContext;
import org.elasticsearch.script.Script; import org.elasticsearch.script.Script;
import org.elasticsearch.script.ScriptType; import org.elasticsearch.script.ScriptType;
import org.elasticsearch.search.DocValueFormat; import org.elasticsearch.search.DocValueFormat;
import org.elasticsearch.search.MultiValueMode;
import org.elasticsearch.search.sort.ScriptSortBuilder.ScriptSortType; import org.elasticsearch.search.sort.ScriptSortBuilder.ScriptSortType;
import java.io.IOException; import java.io.IOException;
@ -34,6 +45,7 @@ import java.util.HashSet;
import java.util.Set; import java.util.Set;
import static org.elasticsearch.search.sort.NestedSortBuilderTests.createRandomNestedSort; import static org.elasticsearch.search.sort.NestedSortBuilderTests.createRandomNestedSort;
import static org.hamcrest.Matchers.instanceOf;
public class ScriptSortBuilderTests extends AbstractSortTestCase<ScriptSortBuilder> { public class ScriptSortBuilderTests extends AbstractSortTestCase<ScriptSortBuilder> {
@ -44,7 +56,7 @@ public class ScriptSortBuilderTests extends AbstractSortTestCase<ScriptSortBuild
public static ScriptSortBuilder randomScriptSortBuilder() { public static ScriptSortBuilder randomScriptSortBuilder() {
ScriptSortType type = randomBoolean() ? ScriptSortType.NUMBER : ScriptSortType.STRING; ScriptSortType type = randomBoolean() ? ScriptSortType.NUMBER : ScriptSortType.STRING;
ScriptSortBuilder builder = new ScriptSortBuilder(mockScript("dummy"), ScriptSortBuilder builder = new ScriptSortBuilder(mockScript(MOCK_SCRIPT_NAME),
type); type);
if (randomBoolean()) { if (randomBoolean()) {
builder.order(randomFrom(SortOrder.values())); builder.order(randomFrom(SortOrder.values()));
@ -237,12 +249,112 @@ public class ScriptSortBuilderTests extends AbstractSortTestCase<ScriptSortBuild
* script sort of type {@link ScriptSortType} does not work with {@link SortMode#AVG}, {@link SortMode#MEDIAN} or {@link SortMode#SUM} * script sort of type {@link ScriptSortType} does not work with {@link SortMode#AVG}, {@link SortMode#MEDIAN} or {@link SortMode#SUM}
*/ */
public void testBadSortMode() throws IOException { public void testBadSortMode() throws IOException {
ScriptSortBuilder builder = new ScriptSortBuilder(mockScript("something"), ScriptSortType.STRING); ScriptSortBuilder builder = new ScriptSortBuilder(mockScript(MOCK_SCRIPT_NAME), ScriptSortType.STRING);
String sortMode = randomFrom(new String[] { "avg", "median", "sum" }); String sortMode = randomFrom(new String[] { "avg", "median", "sum" });
Exception e = expectThrows(IllegalArgumentException.class, () -> builder.sortMode(SortMode.fromString(sortMode))); Exception e = expectThrows(IllegalArgumentException.class, () -> builder.sortMode(SortMode.fromString(sortMode)));
assertEquals("script sort of type [string] doesn't support mode [" + sortMode + "]", e.getMessage()); assertEquals("script sort of type [string] doesn't support mode [" + sortMode + "]", e.getMessage());
} }
/**
* Test that the sort builder mode gets transfered correctly to the SortField
*/
public void testMultiValueMode() throws IOException {
QueryShardContext shardContextMock = createMockShardContext();
for (SortMode mode : SortMode.values()) {
ScriptSortBuilder sortBuilder = new ScriptSortBuilder(mockScript(MOCK_SCRIPT_NAME), ScriptSortType.NUMBER);
sortBuilder.sortMode(mode);
SortField sortField = sortBuilder.build(shardContextMock).field;
assertThat(sortField.getComparatorSource(), instanceOf(XFieldComparatorSource.class));
XFieldComparatorSource comparatorSource = (XFieldComparatorSource) sortField.getComparatorSource();
assertEquals(MultiValueMode.fromString(mode.toString()), comparatorSource.sortMode());
}
// check that without mode set, order ASC sets mode to MIN, DESC to MAX
ScriptSortBuilder sortBuilder = new ScriptSortBuilder(mockScript(MOCK_SCRIPT_NAME), ScriptSortType.NUMBER);
sortBuilder.order(SortOrder.ASC);
SortField sortField = sortBuilder.build(shardContextMock).field;
assertThat(sortField.getComparatorSource(), instanceOf(XFieldComparatorSource.class));
XFieldComparatorSource comparatorSource = (XFieldComparatorSource) sortField.getComparatorSource();
assertEquals(MultiValueMode.MIN, comparatorSource.sortMode());
sortBuilder = new ScriptSortBuilder(mockScript(MOCK_SCRIPT_NAME), ScriptSortType.NUMBER);
sortBuilder.order(SortOrder.DESC);
sortField = sortBuilder.build(shardContextMock).field;
assertThat(sortField.getComparatorSource(), instanceOf(XFieldComparatorSource.class));
comparatorSource = (XFieldComparatorSource) sortField.getComparatorSource();
assertEquals(MultiValueMode.MAX, comparatorSource.sortMode());
}
/**
* Test that the correct comparator sort is returned, based on the script type
*/
public void testBuildCorrectComparatorType() throws IOException {
ScriptSortBuilder sortBuilder = new ScriptSortBuilder(mockScript(MOCK_SCRIPT_NAME), ScriptSortType.STRING);
SortField sortField = sortBuilder.build(createMockShardContext()).field;
assertThat(sortField.getComparatorSource(), instanceOf(BytesRefFieldComparatorSource.class));
sortBuilder = new ScriptSortBuilder(mockScript(MOCK_SCRIPT_NAME), ScriptSortType.NUMBER);
sortField = sortBuilder.build(createMockShardContext()).field;
assertThat(sortField.getComparatorSource(), instanceOf(DoubleValuesComparatorSource.class));
}
/**
* Test that the sort builder nested object gets created in the SortField
*/
public void testBuildNested() throws IOException {
QueryShardContext shardContextMock = createMockShardContext();
ScriptSortBuilder sortBuilder = new ScriptSortBuilder(mockScript(MOCK_SCRIPT_NAME), ScriptSortType.NUMBER)
.setNestedSort(new NestedSortBuilder("path").setFilter(QueryBuilders.matchAllQuery()));
SortField sortField = sortBuilder.build(shardContextMock).field;
assertThat(sortField.getComparatorSource(), instanceOf(XFieldComparatorSource.class));
XFieldComparatorSource comparatorSource = (XFieldComparatorSource) sortField.getComparatorSource();
Nested nested = comparatorSource.nested();
assertNotNull(nested);
assertEquals(new MatchAllDocsQuery(), nested.getInnerQuery());
sortBuilder = new ScriptSortBuilder(mockScript(MOCK_SCRIPT_NAME), ScriptSortType.NUMBER).setNestedPath("path");
sortField = sortBuilder.build(shardContextMock).field;
assertThat(sortField.getComparatorSource(), instanceOf(XFieldComparatorSource.class));
comparatorSource = (XFieldComparatorSource) sortField.getComparatorSource();
nested = comparatorSource.nested();
assertNotNull(nested);
assertEquals(new TermQuery(new Term(TypeFieldMapper.NAME, "__path")), nested.getInnerQuery());
sortBuilder = new ScriptSortBuilder(mockScript(MOCK_SCRIPT_NAME), ScriptSortType.NUMBER).setNestedPath("path")
.setNestedFilter(QueryBuilders.matchAllQuery());
sortField = sortBuilder.build(shardContextMock).field;
assertThat(sortField.getComparatorSource(), instanceOf(XFieldComparatorSource.class));
comparatorSource = (XFieldComparatorSource) sortField.getComparatorSource();
nested = comparatorSource.nested();
assertNotNull(nested);
assertEquals(new MatchAllDocsQuery(), nested.getInnerQuery());
// if nested path is missing, we omit nested element in the comparator
sortBuilder = new ScriptSortBuilder(mockScript(MOCK_SCRIPT_NAME), ScriptSortType.NUMBER)
.setNestedFilter(QueryBuilders.matchAllQuery());
sortField = sortBuilder.build(shardContextMock).field;
assertThat(sortField.getComparatorSource(), instanceOf(XFieldComparatorSource.class));
comparatorSource = (XFieldComparatorSource) sortField.getComparatorSource();
assertNull(comparatorSource.nested());
}
/**
* Test we can either set nested sort via path/filter or via nested sort builder, not both
*/
public void testNestedSortBothThrows() throws IOException {
ScriptSortBuilder sortBuilder = new ScriptSortBuilder(mockScript(MOCK_SCRIPT_NAME), ScriptSortType.NUMBER);
IllegalArgumentException iae = expectThrows(IllegalArgumentException.class,
() -> sortBuilder.setNestedPath("nestedPath").setNestedSort(new NestedSortBuilder("otherPath")));
assertEquals("Setting both nested_path/nested_filter and nested not allowed", iae.getMessage());
iae = expectThrows(IllegalArgumentException.class,
() -> sortBuilder.setNestedSort(new NestedSortBuilder("otherPath")).setNestedPath("nestedPath"));
assertEquals("Setting both nested_path/nested_filter and nested not allowed", iae.getMessage());
iae = expectThrows(IllegalArgumentException.class,
() -> sortBuilder.setNestedSort(new NestedSortBuilder("otherPath")).setNestedFilter(QueryBuilders.matchAllQuery()));
assertEquals("Setting both nested_path/nested_filter and nested not allowed", iae.getMessage());
}
@Override @Override
protected ScriptSortBuilder fromXContent(XContentParser parser, String fieldName) throws IOException { protected ScriptSortBuilder fromXContent(XContentParser parser, String fieldName) throws IOException {
return ScriptSortBuilder.fromXContent(parser, fieldName); return ScriptSortBuilder.fromXContent(parser, fieldName);