From 265964863fbb163e72d8f181887da227dbbd5d4e Mon Sep 17 00:00:00 2001 From: Nik Everett Date: Mon, 13 Jul 2020 17:23:03 -0400 Subject: [PATCH] Add term query for keyword script fields (#59372) This adds what I think is just about the simplest possible `term` query implementation for `keyword` script fields and wires it into the field mapper that we build for them. --- .../StringScriptFieldScript.java | 24 +++-- .../fielddata/ScriptBinaryDocValues.java | 20 ++-- .../fielddata/ScriptBinaryFieldData.java | 5 +- .../mapper/RuntimeKeywordMappedFieldType.java | 11 ++- .../query/StringScriptFieldTermQuery.java | 97 +++++++++++++++++++ .../DoubleScriptFieldScriptTests.java | 19 ++-- .../LongScriptFieldScriptTests.java | 15 ++- .../ScriptFieldScriptTestCase.java | 12 +-- .../StringScriptFieldScriptTests.java | 11 +-- .../RuntimeKeywordMappedFieldTypeTests.java | 72 ++++++++++++++ .../test/runtime_fields/10_keyword.yml | 53 ++++++++++ 11 files changed, 291 insertions(+), 48 deletions(-) create mode 100644 x-pack/plugin/runtime-fields/src/main/java/org/elasticsearch/xpack/runtimefields/query/StringScriptFieldTermQuery.java create mode 100644 x-pack/plugin/runtime-fields/src/test/java/org/elasticsearch/xpack/runtimefields/mapper/RuntimeKeywordMappedFieldTypeTests.java create mode 100644 x-pack/plugin/src/test/resources/rest-api-spec/test/runtime_fields/10_keyword.yml diff --git a/x-pack/plugin/runtime-fields/src/main/java/org/elasticsearch/xpack/runtimefields/StringScriptFieldScript.java b/x-pack/plugin/runtime-fields/src/main/java/org/elasticsearch/xpack/runtimefields/StringScriptFieldScript.java index 391be84b8d964..09321de9a225d 100644 --- a/x-pack/plugin/runtime-fields/src/main/java/org/elasticsearch/xpack/runtimefields/StringScriptFieldScript.java +++ b/x-pack/plugin/runtime-fields/src/main/java/org/elasticsearch/xpack/runtimefields/StringScriptFieldScript.java @@ -14,9 +14,9 @@ import org.elasticsearch.search.lookup.SearchLookup; import java.io.IOException; +import java.util.ArrayList; import java.util.List; import java.util.Map; -import java.util.function.Consumer; public abstract class StringScriptFieldScript extends AbstractScriptFieldScript { public static final ScriptContext CONTEXT = new ScriptContext<>("string_script_field", Factory.class); @@ -32,14 +32,26 @@ public interface Factory extends ScriptFactory { } public interface LeafFactory { - StringScriptFieldScript newInstance(LeafReaderContext ctx, Consumer sync) throws IOException; + StringScriptFieldScript newInstance(LeafReaderContext ctx) throws IOException; } - private final Consumer sync; + private final List results = new ArrayList<>(); - public StringScriptFieldScript(Map params, SearchLookup searchLookup, LeafReaderContext ctx, Consumer sync) { + public StringScriptFieldScript(Map params, SearchLookup searchLookup, LeafReaderContext ctx) { super(params, searchLookup, ctx); - this.sync = sync; + } + + /** + * Execute the script for the provided {@code docId}. + *

+ * @return a mutable {@link List} that contains the results of the script + * and will be modified the next time you call {@linkplain #resultsForDoc}. + */ + public final List resultsForDoc(int docId) { + results.clear(); + setDocument(docId); + execute(); + return results; } public static class Value { @@ -50,7 +62,7 @@ public Value(StringScriptFieldScript script) { } public void value(String v) { - script.sync.accept(v); + script.results.add(v); } } } diff --git a/x-pack/plugin/runtime-fields/src/main/java/org/elasticsearch/xpack/runtimefields/fielddata/ScriptBinaryDocValues.java b/x-pack/plugin/runtime-fields/src/main/java/org/elasticsearch/xpack/runtimefields/fielddata/ScriptBinaryDocValues.java index ae0edeae46b4e..f43ed950b4dd7 100644 --- a/x-pack/plugin/runtime-fields/src/main/java/org/elasticsearch/xpack/runtimefields/fielddata/ScriptBinaryDocValues.java +++ b/x-pack/plugin/runtime-fields/src/main/java/org/elasticsearch/xpack/runtimefields/fielddata/ScriptBinaryDocValues.java @@ -9,30 +9,26 @@ import org.elasticsearch.index.fielddata.SortingBinaryDocValues; import org.elasticsearch.xpack.runtimefields.StringScriptFieldScript; -public final class ScriptBinaryDocValues extends SortingBinaryDocValues { +import java.util.List; +public final class ScriptBinaryDocValues extends SortingBinaryDocValues { private final StringScriptFieldScript script; - private final ScriptBinaryFieldData.ScriptBinaryResult scriptBinaryResult; - ScriptBinaryDocValues(StringScriptFieldScript script, ScriptBinaryFieldData.ScriptBinaryResult scriptBinaryResult) { + ScriptBinaryDocValues(StringScriptFieldScript script) { this.script = script; - this.scriptBinaryResult = scriptBinaryResult; } @Override - public boolean advanceExact(int doc) { - script.setDocument(doc); - script.execute(); - - count = scriptBinaryResult.getResult().size(); + public boolean advanceExact(int docId) { + List results = script.resultsForDoc(docId); + count = results.size(); if (count == 0) { - grow(); return false; } + grow(); int i = 0; - for (String value : scriptBinaryResult.getResult()) { - grow(); + for (String value : results) { values[i++].copyChars(value); } sort(); diff --git a/x-pack/plugin/runtime-fields/src/main/java/org/elasticsearch/xpack/runtimefields/fielddata/ScriptBinaryFieldData.java b/x-pack/plugin/runtime-fields/src/main/java/org/elasticsearch/xpack/runtimefields/fielddata/ScriptBinaryFieldData.java index 42ca35731274b..2a21efc52a387 100644 --- a/x-pack/plugin/runtime-fields/src/main/java/org/elasticsearch/xpack/runtimefields/fielddata/ScriptBinaryFieldData.java +++ b/x-pack/plugin/runtime-fields/src/main/java/org/elasticsearch/xpack/runtimefields/fielddata/ScriptBinaryFieldData.java @@ -102,10 +102,7 @@ public ScriptBinaryLeafFieldData load(LeafReaderContext context) { @Override public ScriptBinaryLeafFieldData loadDirect(LeafReaderContext context) throws IOException { - ScriptBinaryResult scriptBinaryResult = new ScriptBinaryResult(); - return new ScriptBinaryLeafFieldData( - new ScriptBinaryDocValues(leafFactory.get().newInstance(context, scriptBinaryResult::accept), scriptBinaryResult) - ); + return new ScriptBinaryLeafFieldData(new ScriptBinaryDocValues(leafFactory.get().newInstance(context))); } @Override diff --git a/x-pack/plugin/runtime-fields/src/main/java/org/elasticsearch/xpack/runtimefields/mapper/RuntimeKeywordMappedFieldType.java b/x-pack/plugin/runtime-fields/src/main/java/org/elasticsearch/xpack/runtimefields/mapper/RuntimeKeywordMappedFieldType.java index 764d9f0cb61e6..c8680da19a8c5 100644 --- a/x-pack/plugin/runtime-fields/src/main/java/org/elasticsearch/xpack/runtimefields/mapper/RuntimeKeywordMappedFieldType.java +++ b/x-pack/plugin/runtime-fields/src/main/java/org/elasticsearch/xpack/runtimefields/mapper/RuntimeKeywordMappedFieldType.java @@ -8,8 +8,9 @@ import org.apache.lucene.search.Query; import org.apache.lucene.util.BytesRef; -import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.ToXContent.Params; +import org.elasticsearch.common.lucene.BytesRefs; +import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.index.fielddata.IndexFieldData; import org.elasticsearch.index.mapper.MappedFieldType; import org.elasticsearch.index.mapper.TextSearchInfo; @@ -17,9 +18,11 @@ import org.elasticsearch.script.Script; import org.elasticsearch.xpack.runtimefields.StringScriptFieldScript; import org.elasticsearch.xpack.runtimefields.fielddata.ScriptBinaryFieldData; +import org.elasticsearch.xpack.runtimefields.query.StringScriptFieldTermQuery; import java.io.IOException; import java.util.Map; +import java.util.Objects; public final class RuntimeKeywordMappedFieldType extends MappedFieldType { @@ -57,7 +60,11 @@ public IndexFieldData.Builder fielddataBuilder(String fullyQualifiedIndexName) { @Override public Query termQuery(Object value, QueryShardContext context) { - return null; + return new StringScriptFieldTermQuery( + scriptFactory.newFactory(script.getParams(), context.lookup()), + name(), + BytesRefs.toString(Objects.requireNonNull(value)) + ); } @Override diff --git a/x-pack/plugin/runtime-fields/src/main/java/org/elasticsearch/xpack/runtimefields/query/StringScriptFieldTermQuery.java b/x-pack/plugin/runtime-fields/src/main/java/org/elasticsearch/xpack/runtimefields/query/StringScriptFieldTermQuery.java new file mode 100644 index 0000000000000..4d1f319884158 --- /dev/null +++ b/x-pack/plugin/runtime-fields/src/main/java/org/elasticsearch/xpack/runtimefields/query/StringScriptFieldTermQuery.java @@ -0,0 +1,97 @@ +/* + * 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.index.LeafReaderContext; +import org.apache.lucene.index.Term; +import org.apache.lucene.search.ConstantScoreScorer; +import org.apache.lucene.search.ConstantScoreWeight; +import org.apache.lucene.search.DocIdSetIterator; +import org.apache.lucene.search.IndexSearcher; +import org.apache.lucene.search.Query; +import org.apache.lucene.search.QueryVisitor; +import org.apache.lucene.search.ScoreMode; +import org.apache.lucene.search.Scorer; +import org.apache.lucene.search.TwoPhaseIterator; +import org.apache.lucene.search.Weight; +import org.elasticsearch.xpack.runtimefields.StringScriptFieldScript; + +import java.io.IOException; +import java.util.Objects; + +public class StringScriptFieldTermQuery extends Query { + private final StringScriptFieldScript.LeafFactory leafFactory; + private final String fieldName; + private final String term; + + public StringScriptFieldTermQuery(StringScriptFieldScript.LeafFactory leafFactory, String fieldName, String term) { + this.leafFactory = leafFactory; + this.fieldName = fieldName; + this.term = term; + } + + @Override + public final Weight createWeight(IndexSearcher searcher, ScoreMode scoreMode, float boost) throws IOException { + return new ConstantScoreWeight(this, boost) { + @Override + public boolean isCacheable(LeafReaderContext ctx) { + return false; // scripts aren't really cacheable at this point + } + + @Override + public Scorer scorer(LeafReaderContext ctx) throws IOException { + StringScriptFieldScript script = leafFactory.newInstance(ctx); + DocIdSetIterator approximation = DocIdSetIterator.all(ctx.reader().maxDoc()); + TwoPhaseIterator twoPhase = new TwoPhaseIterator(approximation) { + @Override + public boolean matches() throws IOException { + for (String result : script.resultsForDoc(approximation().docID())) { + if (term.equals(result)) { + return true; + } + } + return false; + } + + @Override + public float matchCost() { + // TODO we don't have a good way of estimating the complexity of the script so we just go with 9000 + return 9000f; + } + }; + return new ConstantScoreScorer(this, score(), scoreMode, twoPhase); + } + }; + } + + @Override + public void visit(QueryVisitor visitor) { + visitor.consumeTerms(this, new Term(fieldName, term)); + } + + @Override + public final String toString(String field) { + if (fieldName.contentEquals(field)) { + return term; + } + return fieldName + ":" + term; + } + + @Override + public int hashCode() { + return Objects.hash(fieldName, term); + } + + @Override + public boolean equals(Object obj) { + if (obj == null || getClass() != obj.getClass()) { + return false; + } + StringScriptFieldTermQuery other = (StringScriptFieldTermQuery) obj; + return fieldName.equals(other.fieldName) && term.equals(other.term); + } +} diff --git a/x-pack/plugin/runtime-fields/src/test/java/org/elasticsearch/xpack/runtimefields/DoubleScriptFieldScriptTests.java b/x-pack/plugin/runtime-fields/src/test/java/org/elasticsearch/xpack/runtimefields/DoubleScriptFieldScriptTests.java index 23081d1ff3427..88d1232531e56 100644 --- a/x-pack/plugin/runtime-fields/src/test/java/org/elasticsearch/xpack/runtimefields/DoubleScriptFieldScriptTests.java +++ b/x-pack/plugin/runtime-fields/src/test/java/org/elasticsearch/xpack/runtimefields/DoubleScriptFieldScriptTests.java @@ -19,13 +19,14 @@ import org.elasticsearch.search.lookup.SearchLookup; import java.io.IOException; +import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.function.IntFunction; import static org.hamcrest.Matchers.equalTo; public class DoubleScriptFieldScriptTests extends ScriptFieldScriptTestCase< - DoubleScriptFieldScript, DoubleScriptFieldScript.Factory, DoubleScriptFieldScript.LeafFactory, Double> { @@ -112,11 +113,15 @@ protected DoubleScriptFieldScript.LeafFactory newLeafFactory( } @Override - protected DoubleScriptFieldScript newInstance( - DoubleScriptFieldScript.LeafFactory leafFactory, - LeafReaderContext context, - List result - ) throws IOException { - return leafFactory.newInstance(context, result::add); + protected IntFunction> newInstance(DoubleScriptFieldScript.LeafFactory leafFactory, LeafReaderContext context) + throws IOException { + List results = new ArrayList<>(); + DoubleScriptFieldScript script = leafFactory.newInstance(context, results::add); + return docId -> { + results.clear(); + script.setDocument(docId); + script.execute(); + return results; + }; } } diff --git a/x-pack/plugin/runtime-fields/src/test/java/org/elasticsearch/xpack/runtimefields/LongScriptFieldScriptTests.java b/x-pack/plugin/runtime-fields/src/test/java/org/elasticsearch/xpack/runtimefields/LongScriptFieldScriptTests.java index ef0c5cd304d0e..d8d8da6d715f4 100644 --- a/x-pack/plugin/runtime-fields/src/test/java/org/elasticsearch/xpack/runtimefields/LongScriptFieldScriptTests.java +++ b/x-pack/plugin/runtime-fields/src/test/java/org/elasticsearch/xpack/runtimefields/LongScriptFieldScriptTests.java @@ -18,13 +18,14 @@ import org.elasticsearch.search.lookup.SearchLookup; import java.io.IOException; +import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.function.IntFunction; import static org.hamcrest.Matchers.equalTo; public class LongScriptFieldScriptTests extends ScriptFieldScriptTestCase< - LongScriptFieldScript, LongScriptFieldScript.Factory, LongScriptFieldScript.LeafFactory, Long> { @@ -98,9 +99,15 @@ protected LongScriptFieldScript.LeafFactory newLeafFactory( } @Override - protected LongScriptFieldScript newInstance(LongScriptFieldScript.LeafFactory leafFactory, LeafReaderContext context, List result) + protected IntFunction> newInstance(LongScriptFieldScript.LeafFactory leafFactory, LeafReaderContext context) throws IOException { - - return leafFactory.newInstance(context, result::add); + List results = new ArrayList<>(); + LongScriptFieldScript script = leafFactory.newInstance(context, results::add); + return docId -> { + results.clear(); + script.setDocument(docId); + script.execute(); + return results; + }; } } diff --git a/x-pack/plugin/runtime-fields/src/test/java/org/elasticsearch/xpack/runtimefields/ScriptFieldScriptTestCase.java b/x-pack/plugin/runtime-fields/src/test/java/org/elasticsearch/xpack/runtimefields/ScriptFieldScriptTestCase.java index 3b7a819d04b40..d2580a8ad5b8e 100644 --- a/x-pack/plugin/runtime-fields/src/test/java/org/elasticsearch/xpack/runtimefields/ScriptFieldScriptTestCase.java +++ b/x-pack/plugin/runtime-fields/src/test/java/org/elasticsearch/xpack/runtimefields/ScriptFieldScriptTestCase.java @@ -42,16 +42,17 @@ import java.util.List; import java.util.Map; import java.util.function.Function; +import java.util.function.IntFunction; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -public abstract class ScriptFieldScriptTestCase extends ESTestCase { +public abstract class ScriptFieldScriptTestCase extends ESTestCase { protected abstract ScriptContext scriptContext(); protected abstract LF newLeafFactory(F factory, Map params, SearchLookup searchLookup); - protected abstract S newInstance(LF leafFactory, LeafReaderContext context, List results) throws IOException; + protected abstract IntFunction> newInstance(LF leafFactory, LeafReaderContext context) throws IOException; protected final List execute(CheckedConsumer indexBuilder, String script, MappedFieldType... types) throws IOException { @@ -92,15 +93,14 @@ public ScoreMode scoreMode() { @Override public LeafCollector getLeafCollector(LeafReaderContext context) throws IOException { - S compiled = newInstance(leafFactory, context, result); + IntFunction> compiled = newInstance(leafFactory, context); return new LeafCollector() { @Override public void setScorer(Scorable scorer) {} @Override - public void collect(int doc) { - compiled.setDocument(doc); - compiled.execute(); + public void collect(int docId) { + result.addAll(compiled.apply(docId)); } }; } diff --git a/x-pack/plugin/runtime-fields/src/test/java/org/elasticsearch/xpack/runtimefields/StringScriptFieldScriptTests.java b/x-pack/plugin/runtime-fields/src/test/java/org/elasticsearch/xpack/runtimefields/StringScriptFieldScriptTests.java index 5ecedd1762647..15d6d225b14e5 100644 --- a/x-pack/plugin/runtime-fields/src/test/java/org/elasticsearch/xpack/runtimefields/StringScriptFieldScriptTests.java +++ b/x-pack/plugin/runtime-fields/src/test/java/org/elasticsearch/xpack/runtimefields/StringScriptFieldScriptTests.java @@ -19,11 +19,11 @@ import java.io.IOException; import java.util.List; import java.util.Map; +import java.util.function.IntFunction; import static org.hamcrest.Matchers.equalTo; public class StringScriptFieldScriptTests extends ScriptFieldScriptTestCase< - StringScriptFieldScript, StringScriptFieldScript.Factory, StringScriptFieldScript.LeafFactory, String> { @@ -104,11 +104,8 @@ protected StringScriptFieldScript.LeafFactory newLeafFactory( } @Override - protected StringScriptFieldScript newInstance( - StringScriptFieldScript.LeafFactory leafFactory, - LeafReaderContext context, - List result - ) throws IOException { - return leafFactory.newInstance(context, result::add); + protected IntFunction> newInstance(StringScriptFieldScript.LeafFactory leafFactory, LeafReaderContext context) + throws IOException { + return leafFactory.newInstance(context)::resultsForDoc; } } diff --git a/x-pack/plugin/runtime-fields/src/test/java/org/elasticsearch/xpack/runtimefields/mapper/RuntimeKeywordMappedFieldTypeTests.java b/x-pack/plugin/runtime-fields/src/test/java/org/elasticsearch/xpack/runtimefields/mapper/RuntimeKeywordMappedFieldTypeTests.java new file mode 100644 index 0000000000000..ba09555da47d5 --- /dev/null +++ b/x-pack/plugin/runtime-fields/src/test/java/org/elasticsearch/xpack/runtimefields/mapper/RuntimeKeywordMappedFieldTypeTests.java @@ -0,0 +1,72 @@ +/* + * 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.mapper; + +import org.apache.lucene.document.StoredField; +import org.apache.lucene.index.DirectoryReader; +import org.apache.lucene.index.RandomIndexWriter; +import org.apache.lucene.search.IndexSearcher; +import org.apache.lucene.store.Directory; +import org.apache.lucene.util.BytesRef; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.index.mapper.MapperService; +import org.elasticsearch.index.query.QueryShardContext; +import org.elasticsearch.painless.PainlessPlugin; +import org.elasticsearch.plugins.ExtensiblePlugin.ExtensionLoader; +import org.elasticsearch.script.Script; +import org.elasticsearch.script.ScriptModule; +import org.elasticsearch.script.ScriptService; +import org.elasticsearch.search.lookup.SearchLookup; +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.xpack.runtimefields.RuntimeFields; +import org.elasticsearch.xpack.runtimefields.RuntimeFieldsPainlessExtension; +import org.elasticsearch.xpack.runtimefields.StringScriptFieldScript; + +import java.io.IOException; +import java.util.List; + +import static java.util.Collections.emptyMap; +import static org.hamcrest.Matchers.equalTo; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class RuntimeKeywordMappedFieldTypeTests extends ESTestCase { + public void testTermQuery() throws IOException { + try (Directory directory = newDirectory(); RandomIndexWriter iw = new RandomIndexWriter(random(), directory)) { + iw.addDocument(List.of(new StoredField("_source", new BytesRef("{\"foo\": 1}")))); + iw.addDocument(List.of(new StoredField("_source", new BytesRef("{\"foo\": 2}")))); + try (DirectoryReader reader = iw.getReader()) { + IndexSearcher searcher = newSearcher(reader); + assertThat(searcher.count(build("value(source.foo.toString())").termQuery("1", mockContext())), equalTo(1)); + } + } + } + + private RuntimeKeywordMappedFieldType build(String code) throws IOException { + Script script = new Script(code); + PainlessPlugin painlessPlugin = new PainlessPlugin(); + painlessPlugin.loadExtensions(new ExtensionLoader() { + @Override + @SuppressWarnings("unchecked") // We only ever load painless extensions here so it is fairly safe. + public List loadExtensions(Class extensionPointType) { + return (List) List.of(new RuntimeFieldsPainlessExtension()); + } + }); + ScriptModule scriptModule = new ScriptModule(Settings.EMPTY, List.of(painlessPlugin, new RuntimeFields())); + try (ScriptService scriptService = new ScriptService(Settings.EMPTY, scriptModule.engines, scriptModule.contexts)) { + StringScriptFieldScript.Factory factory = scriptService.compile(script, StringScriptFieldScript.CONTEXT); + return new RuntimeKeywordMappedFieldType("test", script, factory, emptyMap()); + } + } + + private QueryShardContext mockContext() { + MapperService mapperService = mock(MapperService.class); + QueryShardContext context = mock(QueryShardContext.class); + when(context.lookup()).thenReturn(new SearchLookup(mapperService, mft -> null)); + return context; + } +} 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 new file mode 100644 index 0000000000000..5d30364789b5b --- /dev/null +++ b/x-pack/plugin/src/test/resources/rest-api-spec/test/runtime_fields/10_keyword.yml @@ -0,0 +1,53 @@ +--- +setup: + - do: + indices.create: + index: sensor + body: + settings: + number_of_shards: 1 + number_of_replicas: 0 + mappings: + properties: + timestamp: + type: date + temperature: + type: long + voltage: + type: float + node: + type: keyword + day_of_week: + type: script + runtime_type: keyword + script: value(doc['timestamp'].value.dayOfWeekEnum.getDisplayName(TextStyle.FULL, Locale.ROOT)) + + - do: + bulk: + index: sensor + refresh: true + body: | + {"index":{}} + {"timestamp": 1516729294000, "temperature": 200, "voltage": 5.2, "node": "a"} + {"index":{}} + {"timestamp": 1516642894000, "temperature": 201, "voltage": 5.8, "node": "b"} + {"index":{}} + {"timestamp": 1516556494000, "temperature": 202, "voltage": 5.1, "node": "a"} + {"index":{}} + {"timestamp": 1516470094000, "temperature": 198, "voltage": 5.6, "node": "b"} + {"index":{}} + {"timestamp": 1516383694000, "temperature": 200, "voltage": 4.2, "node": "c"} + {"index":{}} + {"timestamp": 1516297294000, "temperature": 202, "voltage": 4.0, "node": "c"} + +--- +"term query": + - do: + search: + index: sensor + body: + query: + term: + day_of_week: Monday + - match: {hits.total.value: 1} + - match: {hits.hits.0._source.voltage: 5.8}