diff --git a/x-pack/plugin/runtime-fields/src/main/java/org/elasticsearch/xpack/runtimefields/query/AbstractScriptFieldQuery.java b/x-pack/plugin/runtime-fields/src/main/java/org/elasticsearch/xpack/runtimefields/query/AbstractScriptFieldQuery.java index dc4c0ec318a6c..82b2e63485238 100644 --- a/x-pack/plugin/runtime-fields/src/main/java/org/elasticsearch/xpack/runtimefields/query/AbstractScriptFieldQuery.java +++ b/x-pack/plugin/runtime-fields/src/main/java/org/elasticsearch/xpack/runtimefields/query/AbstractScriptFieldQuery.java @@ -10,6 +10,7 @@ import org.apache.lucene.search.ConstantScoreScorer; import org.apache.lucene.search.ConstantScoreWeight; import org.apache.lucene.search.DocIdSetIterator; +import org.apache.lucene.search.Explanation; import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.search.Query; import org.apache.lucene.search.ScoreMode; @@ -80,6 +81,15 @@ public float matchCost() { }; return new ConstantScoreScorer(this, score(), scoreMode, twoPhase); } + + @Override + public Explanation explain(LeafReaderContext context, int doc) throws IOException { + Explanation constantExplanation = super.explain(context, doc); + if (constantExplanation.isMatch()) { + return explainMatch(boost, constantExplanation.getDescription()); + } + return constantExplanation; + } }; } @@ -98,4 +108,17 @@ public boolean equals(Object obj) { AbstractScriptFieldQuery other = (AbstractScriptFieldQuery) obj; return script.equals(other.script) && fieldName.equals(other.fieldName); } + + final Explanation explainMatch(float boost, String description) { + return Explanation.match( + boost, + description, + Explanation.match( + boost, + "boost * runtime_field_score", + Explanation.match(boost, "boost"), + Explanation.match(1.0, "runtime_field_score is always 1") + ) + ); + } } diff --git a/x-pack/plugin/runtime-fields/src/test/java/org/elasticsearch/xpack/runtimefields/query/AbstractScriptFieldQueryTests.java b/x-pack/plugin/runtime-fields/src/test/java/org/elasticsearch/xpack/runtimefields/query/AbstractScriptFieldQueryTests.java new file mode 100644 index 0000000000000..096e01b287612 --- /dev/null +++ b/x-pack/plugin/runtime-fields/src/test/java/org/elasticsearch/xpack/runtimefields/query/AbstractScriptFieldQueryTests.java @@ -0,0 +1,53 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +package org.elasticsearch.xpack.runtimefields.query; + +import org.apache.lucene.search.Explanation; +import org.elasticsearch.script.Script; +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.xpack.runtimefields.mapper.AbstractFieldScript; + +import java.io.IOException; + +import static org.hamcrest.Matchers.equalTo; + +public class AbstractScriptFieldQueryTests extends ESTestCase { + public void testExplainMatched() throws IOException { + AbstractScriptFieldQuery query = new AbstractScriptFieldQuery( + new Script("test"), + "test", + null + ) { + @Override + protected boolean matches(AbstractFieldScript scriptContext, int docId) { + throw new UnsupportedOperationException(); + } + + @Override + public String toString(String field) { + throw new UnsupportedOperationException(); + } + }; + float boost = randomBoolean() ? 1.0f : randomFloat(); + String dummyDescription = randomAlphaOfLength(10); + assertThat( + query.explainMatch(boost, dummyDescription), + equalTo( + Explanation.match( + boost, + dummyDescription, + Explanation.match( + boost, + "boost * runtime_field_score", + Explanation.match(boost, "boost"), + Explanation.match(1.0, "runtime_field_score is always 1") + ) + ) + ) + ); + } +} diff --git a/x-pack/plugin/src/test/resources/rest-api-spec/test/runtime_fields/10_keyword.yml b/x-pack/plugin/src/test/resources/rest-api-spec/test/runtime_fields/10_keyword.yml index 1bd38e88bb88c..94df6f4996b08 100644 --- a/x-pack/plugin/src/test/resources/rest-api-spec/test/runtime_fields/10_keyword.yml +++ b/x-pack/plugin/src/test/resources/rest-api-spec/test/runtime_fields/10_keyword.yml @@ -155,6 +155,76 @@ setup: - match: {hits.total.value: 1} - match: {hits.hits.0._source.voltage: 5.8} +--- +"explain term query": + - do: + search: + index: sensor + explain: true + body: + query: + term: + day_of_week: Monday + - match: {hits.hits.0._explanation.value: 1.0} + - match: {hits.hits.0._explanation.description: day_of_week:Monday} + - match: {hits.hits.0._explanation.details.0.value: 1.0} + - match: {hits.hits.0._explanation.details.0.description: 'boost * runtime_field_score'} + - match: {hits.hits.0._explanation.details.0.details.0.value: 1.0} + - match: {hits.hits.0._explanation.details.0.details.0.description: 'boost'} + - match: {hits.hits.0._explanation.details.0.details.1.value: 1.0} + - match: {hits.hits.0._explanation.details.0.details.1.description: 'runtime_field_score is always 1'} + +--- +"explain term query with boost": + - do: + search: + index: sensor + explain: true + body: + query: + term: + day_of_week: + value: Monday + boost: 7 + - match: {hits.hits.0._explanation.value: 7.0} + - match: {hits.hits.0._explanation.description: day_of_week:Monday^7.0} + - match: {hits.hits.0._explanation.details.0.value: 7.0} + - match: {hits.hits.0._explanation.details.0.description: 'boost * runtime_field_score'} + - match: {hits.hits.0._explanation.details.0.details.0.value: 7.0} + - match: {hits.hits.0._explanation.details.0.details.0.description: 'boost'} + - match: {hits.hits.0._explanation.details.0.details.1.value: 1.0} + - match: {hits.hits.0._explanation.details.0.details.1.description: 'runtime_field_score is always 1'} + +--- +"explain term query wrapped in script score": + - do: + search: + index: sensor + explain: true + body: + query: + script_score: + script: + source: _score * 1000 + query: + term: + day_of_week: + value: Monday + boost: 7 + - match: {hits.hits.0._explanation.value: 7000.0} + - match: {hits.hits.0._explanation.description: "script score function, computed with script:\"Script{type=inline, lang='painless', idOrCode='_score * 1000', options={}, params={}}\""} + - match: {hits.hits.0._explanation.details.0.value: 7.0} + - match: {hits.hits.0._explanation.details.0.description: '_score: '} + - match: {hits.hits.0._explanation.details.0.details.0.value: 7.0} + - match: {hits.hits.0._explanation.details.0.details.0.description: day_of_week:Monday^7.0} + - match: {hits.hits.0._explanation.details.0.details.0.details.0.value: 7.0} + - match: {hits.hits.0._explanation.details.0.details.0.details.0.description: 'boost * runtime_field_score'} + - match: {hits.hits.0._explanation.details.0.details.0.details.0.details.0.value: 7.0} + - match: {hits.hits.0._explanation.details.0.details.0.details.0.details.0.description: 'boost'} + - match: {hits.hits.0._explanation.details.0.details.0.details.0.details.1.value: 1.0} + - match: {hits.hits.0._explanation.details.0.details.0.details.0.details.1.description: 'runtime_field_score is always 1'} + + --- "match query": - do: diff --git a/x-pack/plugin/src/test/resources/rest-api-spec/test/runtime_fields/20_long.yml b/x-pack/plugin/src/test/resources/rest-api-spec/test/runtime_fields/20_long.yml index 45fb63a51d24b..bf211aceb704a 100644 --- a/x-pack/plugin/src/test/resources/rest-api-spec/test/runtime_fields/20_long.yml +++ b/x-pack/plugin/src/test/resources/rest-api-spec/test/runtime_fields/20_long.yml @@ -172,6 +172,25 @@ setup: - match: {hits.total.value: 1} - match: {hits.hits.0._source.voltage: 5.8} +--- +"explain term query": + - do: + search: + index: sensor + explain: true + body: + query: + term: + voltage_times_ten: 58 + - match: {hits.hits.0._explanation.value: 1.0} + - match: {hits.hits.0._explanation.description: voltage_times_ten:58} + - match: {hits.hits.0._explanation.details.0.value: 1.0} + - match: {hits.hits.0._explanation.details.0.description: 'boost * runtime_field_score'} + - match: {hits.hits.0._explanation.details.0.details.0.value: 1.0} + - match: {hits.hits.0._explanation.details.0.details.0.description: 'boost'} + - match: {hits.hits.0._explanation.details.0.details.1.value: 1.0} + - match: {hits.hits.0._explanation.details.0.details.1.description: 'runtime_field_score is always 1'} + --- "nested": - do: diff --git a/x-pack/plugin/src/test/resources/rest-api-spec/test/runtime_fields/30_double.yml b/x-pack/plugin/src/test/resources/rest-api-spec/test/runtime_fields/30_double.yml index 12b197519c6ce..db43647dc627a 100644 --- a/x-pack/plugin/src/test/resources/rest-api-spec/test/runtime_fields/30_double.yml +++ b/x-pack/plugin/src/test/resources/rest-api-spec/test/runtime_fields/30_double.yml @@ -194,3 +194,22 @@ setup: voltage_percent: 1.0 - match: {hits.total.value: 1} - match: {hits.hits.0._source.voltage: 5.8} + +--- +"explain term query": + - do: + search: + index: sensor + explain: true + body: + query: + term: + voltage_percent: 1.0 + - match: {hits.hits.0._explanation.value: 1.0} + - match: {hits.hits.0._explanation.description: voltage_percent:1.0} + - match: {hits.hits.0._explanation.details.0.value: 1.0} + - match: {hits.hits.0._explanation.details.0.description: 'boost * runtime_field_score'} + - match: {hits.hits.0._explanation.details.0.details.0.value: 1.0} + - match: {hits.hits.0._explanation.details.0.details.0.description: 'boost'} + - match: {hits.hits.0._explanation.details.0.details.1.value: 1.0} + - match: {hits.hits.0._explanation.details.0.details.1.description: 'runtime_field_score is always 1'} diff --git a/x-pack/plugin/src/test/resources/rest-api-spec/test/runtime_fields/40_date.yml b/x-pack/plugin/src/test/resources/rest-api-spec/test/runtime_fields/40_date.yml index f41828400f42e..a1a25302e4d93 100644 --- a/x-pack/plugin/src/test/resources/rest-api-spec/test/runtime_fields/40_date.yml +++ b/x-pack/plugin/src/test/resources/rest-api-spec/test/runtime_fields/40_date.yml @@ -183,3 +183,22 @@ setup: tomorrow: 2018-01-19T17:41:34Z - match: {hits.total.value: 1} - match: {hits.hits.0._source.voltage: 4.0} + +--- +"explain term query": + - do: + search: + index: sensor + explain: true + body: + query: + term: + tomorrow: 2018-01-19T17:41:34Z + - match: {hits.hits.0._explanation.value: 1.0} + - match: {hits.hits.0._explanation.description: tomorrow:1516383694000} + - match: {hits.hits.0._explanation.details.0.value: 1.0} + - match: {hits.hits.0._explanation.details.0.description: 'boost * runtime_field_score'} + - match: {hits.hits.0._explanation.details.0.details.0.value: 1.0} + - match: {hits.hits.0._explanation.details.0.details.0.description: 'boost'} + - match: {hits.hits.0._explanation.details.0.details.1.value: 1.0} + - match: {hits.hits.0._explanation.details.0.details.1.description: 'runtime_field_score is always 1'} diff --git a/x-pack/plugin/src/test/resources/rest-api-spec/test/runtime_fields/50_ip.yml b/x-pack/plugin/src/test/resources/rest-api-spec/test/runtime_fields/50_ip.yml index 843ea2cda257b..fdbad9e2241c9 100644 --- a/x-pack/plugin/src/test/resources/rest-api-spec/test/runtime_fields/50_ip.yml +++ b/x-pack/plugin/src/test/resources/rest-api-spec/test/runtime_fields/50_ip.yml @@ -160,4 +160,21 @@ setup: - match: {hits.total.value: 1} - match: {hits.hits.0._source.timestamp: "1998-04-30T14:31:27-05:00"} -# TODO tests for using the ip in a script. there is almost certainly whitelist "fun" here. +--- +"explain term query": + - do: + search: + index: http_logs + explain: true + body: + query: + term: + ip: 252.0.0.0 + - match: {hits.hits.0._explanation.value: 1.0} + - match: {hits.hits.0._explanation.description: ip:252.0.0.0} + - match: {hits.hits.0._explanation.details.0.value: 1.0} + - match: {hits.hits.0._explanation.details.0.description: 'boost * runtime_field_score'} + - match: {hits.hits.0._explanation.details.0.details.0.value: 1.0} + - match: {hits.hits.0._explanation.details.0.details.0.description: 'boost'} + - match: {hits.hits.0._explanation.details.0.details.1.value: 1.0} + - match: {hits.hits.0._explanation.details.0.details.1.description: 'runtime_field_score is always 1'} diff --git a/x-pack/plugin/src/test/resources/rest-api-spec/test/runtime_fields/60_boolean.yml b/x-pack/plugin/src/test/resources/rest-api-spec/test/runtime_fields/60_boolean.yml index aaaccaf6c1cd3..c14fe7084a851 100644 --- a/x-pack/plugin/src/test/resources/rest-api-spec/test/runtime_fields/60_boolean.yml +++ b/x-pack/plugin/src/test/resources/rest-api-spec/test/runtime_fields/60_boolean.yml @@ -135,3 +135,22 @@ setup: timestamp: asc - match: {hits.total.value: 4} - match: {hits.hits.0._source.voltage: 5.6} + +--- +"explain term query": + - do: + search: + index: sensor + explain: true + body: + query: + term: + over_v: true + - match: {hits.hits.0._explanation.value: 1.0} + - match: {hits.hits.0._explanation.description: over_v:true} + - match: {hits.hits.0._explanation.details.0.value: 1.0} + - match: {hits.hits.0._explanation.details.0.description: 'boost * runtime_field_score'} + - match: {hits.hits.0._explanation.details.0.details.0.value: 1.0} + - match: {hits.hits.0._explanation.details.0.details.0.description: 'boost'} + - match: {hits.hits.0._explanation.details.0.details.1.value: 1.0} + - match: {hits.hits.0._explanation.details.0.details.1.description: 'runtime_field_score is always 1'}