From c9b9b73306c7b6cb1e7059b880c817c24d0647e5 Mon Sep 17 00:00:00 2001 From: Nik Everett Date: Tue, 14 Jul 2020 15:46:13 -0400 Subject: [PATCH] Two queries for keyword script field (#59527) This adds the `exits` and `terms` query for the `keyword` style of scripted field. --- .../mapper/RuntimeKeywordMappedFieldType.java | 26 ++++-- .../query/AbstractStringScriptFieldQuery.java | 91 +++++++++++++++++++ .../query/StringScriptFieldExistsQuery.java | 32 +++++++ .../query/StringScriptFieldTermQuery.java | 73 ++++----------- .../query/StringScriptFieldTermsQuery.java | 69 ++++++++++++++ .../RuntimeKeywordMappedFieldTypeTests.java | 24 +++++ ...bstractStringScriptFieldQueryTestCase.java | 37 ++++++++ .../StringScriptFieldExistsQueryTests.java | 53 +++++++++++ .../StringScriptFieldTermQueryTests.java | 62 +++++++++++++ .../StringScriptFieldTermsQueryTests.java | 66 ++++++++++++++ .../test/runtime_fields/10_keyword.yml | 36 ++++++++ 11 files changed, 509 insertions(+), 60 deletions(-) create mode 100644 x-pack/plugin/runtime-fields/src/main/java/org/elasticsearch/xpack/runtimefields/query/AbstractStringScriptFieldQuery.java create mode 100644 x-pack/plugin/runtime-fields/src/main/java/org/elasticsearch/xpack/runtimefields/query/StringScriptFieldExistsQuery.java create mode 100644 x-pack/plugin/runtime-fields/src/main/java/org/elasticsearch/xpack/runtimefields/query/StringScriptFieldTermsQuery.java create mode 100644 x-pack/plugin/runtime-fields/src/test/java/org/elasticsearch/xpack/runtimefields/query/AbstractStringScriptFieldQueryTestCase.java create mode 100644 x-pack/plugin/runtime-fields/src/test/java/org/elasticsearch/xpack/runtimefields/query/StringScriptFieldExistsQueryTests.java create mode 100644 x-pack/plugin/runtime-fields/src/test/java/org/elasticsearch/xpack/runtimefields/query/StringScriptFieldTermQueryTests.java create mode 100644 x-pack/plugin/runtime-fields/src/test/java/org/elasticsearch/xpack/runtimefields/query/StringScriptFieldTermsQueryTests.java 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 3cf83f3aad33b..16261bbfe3173 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 @@ -17,11 +17,17 @@ import org.elasticsearch.script.Script; import org.elasticsearch.xpack.runtimefields.StringScriptFieldScript; import org.elasticsearch.xpack.runtimefields.fielddata.ScriptBinaryFieldData; +import org.elasticsearch.xpack.runtimefields.query.StringScriptFieldExistsQuery; import org.elasticsearch.xpack.runtimefields.query.StringScriptFieldTermQuery; +import org.elasticsearch.xpack.runtimefields.query.StringScriptFieldTermsQuery; import java.io.IOException; +import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Set; + +import static java.util.stream.Collectors.toSet; public final class RuntimeKeywordMappedFieldType extends MappedFieldType { @@ -57,18 +63,24 @@ public ScriptBinaryFieldData.Builder fielddataBuilder(String fullyQualifiedIndex return new ScriptBinaryFieldData.Builder(scriptFactory); } + private StringScriptFieldScript.LeafFactory leafFactory(QueryShardContext context) { + return scriptFactory.newFactory(script.getParams(), context.lookup()); + } + + @Override + public Query existsQuery(QueryShardContext context) { + return new StringScriptFieldExistsQuery(leafFactory(context), name()); + } + @Override public Query termQuery(Object value, QueryShardContext context) { - return new StringScriptFieldTermQuery( - scriptFactory.newFactory(script.getParams(), context.lookup()), - name(), - BytesRefs.toString(Objects.requireNonNull(value)) - ); + return new StringScriptFieldTermQuery(leafFactory(context), name(), BytesRefs.toString(Objects.requireNonNull(value))); } @Override - public Query existsQuery(QueryShardContext context) { - return null; + public Query termsQuery(List values, QueryShardContext context) { + Set terms = values.stream().map(v -> BytesRefs.toString(Objects.requireNonNull(v))).collect(toSet()); + return new StringScriptFieldTermsQuery(leafFactory(context), name(), terms); } void doXContentBody(XContentBuilder builder, boolean includeDefaults, Params params) throws IOException { diff --git a/x-pack/plugin/runtime-fields/src/main/java/org/elasticsearch/xpack/runtimefields/query/AbstractStringScriptFieldQuery.java b/x-pack/plugin/runtime-fields/src/main/java/org/elasticsearch/xpack/runtimefields/query/AbstractStringScriptFieldQuery.java new file mode 100644 index 0000000000000..c5d1fdaba5714 --- /dev/null +++ b/x-pack/plugin/runtime-fields/src/main/java/org/elasticsearch/xpack/runtimefields/query/AbstractStringScriptFieldQuery.java @@ -0,0 +1,91 @@ +/* + * 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.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.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 org.elasticsearch.xpack.runtimefields.StringScriptFieldScript.LeafFactory; + +import java.io.IOException; +import java.util.List; +import java.util.Objects; + +/** + * Abstract base class for building queries based on {@link StringScriptFieldScript}. + */ +abstract class AbstractStringScriptFieldQuery extends Query { + private final StringScriptFieldScript.LeafFactory leafFactory; + private final String fieldName; + + AbstractStringScriptFieldQuery(LeafFactory leafFactory, String fieldName) { + this.leafFactory = Objects.requireNonNull(leafFactory); + this.fieldName = Objects.requireNonNull(fieldName); + } + + /** + * Does the value match this query? + */ + public abstract boolean matches(List values); + + @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 { + return AbstractStringScriptFieldQuery.this.matches(script.resultsForDoc(approximation().docID())); + } + + @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); + } + }; + } + + protected final String fieldName() { + return fieldName; + } + + @Override + public int hashCode() { + // TODO should leafFactory be here? Something about the script probably should be! + return Objects.hash(getClass(), fieldName); + } + + @Override + public boolean equals(Object obj) { + if (obj == null || getClass() != obj.getClass()) { + return false; + } + // TODO should leafFactory be here? Something about the script probably should be! + AbstractStringScriptFieldQuery other = (AbstractStringScriptFieldQuery) obj; + return fieldName.equals(other.fieldName); + } +} diff --git a/x-pack/plugin/runtime-fields/src/main/java/org/elasticsearch/xpack/runtimefields/query/StringScriptFieldExistsQuery.java b/x-pack/plugin/runtime-fields/src/main/java/org/elasticsearch/xpack/runtimefields/query/StringScriptFieldExistsQuery.java new file mode 100644 index 0000000000000..7a83553cba33c --- /dev/null +++ b/x-pack/plugin/runtime-fields/src/main/java/org/elasticsearch/xpack/runtimefields/query/StringScriptFieldExistsQuery.java @@ -0,0 +1,32 @@ +/* + * 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.elasticsearch.xpack.runtimefields.StringScriptFieldScript; + +import java.util.List; + +public class StringScriptFieldExistsQuery extends AbstractStringScriptFieldQuery { + public StringScriptFieldExistsQuery(StringScriptFieldScript.LeafFactory leafFactory, String fieldName) { + super(leafFactory, fieldName); + } + + @Override + public boolean matches(List values) { + return false == values.isEmpty(); + } + + @Override + public final String toString(String field) { + if (fieldName().contentEquals(field)) { + return "*"; + } + return fieldName() + ":*"; + } + + // Superclass's equals and hashCode are great for this class +} 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 index 4d1f319884158..097eacdf7cb44 100644 --- 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 @@ -6,92 +6,59 @@ 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.List; import java.util.Objects; -public class StringScriptFieldTermQuery extends Query { - private final StringScriptFieldScript.LeafFactory leafFactory; - private final String fieldName; +public class StringScriptFieldTermQuery extends AbstractStringScriptFieldQuery { private final String term; public StringScriptFieldTermQuery(StringScriptFieldScript.LeafFactory leafFactory, String fieldName, String term) { - this.leafFactory = leafFactory; - this.fieldName = fieldName; - this.term = term; + super(leafFactory, fieldName); + this.term = Objects.requireNonNull(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 + public boolean matches(List values) { + for (String value : values) { + if (term.equals(value)) { + return true; } - - @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); - } - }; + } + return false; } @Override public void visit(QueryVisitor visitor) { - visitor.consumeTerms(this, new Term(fieldName, term)); + visitor.consumeTerms(this, new Term(fieldName(), term)); } @Override public final String toString(String field) { - if (fieldName.contentEquals(field)) { + if (fieldName().contentEquals(field)) { return term; } - return fieldName + ":" + term; + return fieldName() + ":" + term; } @Override public int hashCode() { - return Objects.hash(fieldName, term); + return Objects.hash(super.hashCode(), term); } @Override public boolean equals(Object obj) { - if (obj == null || getClass() != obj.getClass()) { + if (false == super.equals(obj)) { return false; } StringScriptFieldTermQuery other = (StringScriptFieldTermQuery) obj; - return fieldName.equals(other.fieldName) && term.equals(other.term); + return term.equals(other.term); + } + + String term() { + return term; } } diff --git a/x-pack/plugin/runtime-fields/src/main/java/org/elasticsearch/xpack/runtimefields/query/StringScriptFieldTermsQuery.java b/x-pack/plugin/runtime-fields/src/main/java/org/elasticsearch/xpack/runtimefields/query/StringScriptFieldTermsQuery.java new file mode 100644 index 0000000000000..eb09ecbcaf9b2 --- /dev/null +++ b/x-pack/plugin/runtime-fields/src/main/java/org/elasticsearch/xpack/runtimefields/query/StringScriptFieldTermsQuery.java @@ -0,0 +1,69 @@ +/* + * 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.Term; +import org.apache.lucene.search.QueryVisitor; +import org.elasticsearch.xpack.runtimefields.StringScriptFieldScript; + +import java.util.List; +import java.util.Objects; +import java.util.Set; + +public class StringScriptFieldTermsQuery extends AbstractStringScriptFieldQuery { + private final Set terms; + + public StringScriptFieldTermsQuery(StringScriptFieldScript.LeafFactory leafFactory, String fieldName, Set terms) { + super(leafFactory, fieldName); + this.terms = terms; + } + + @Override + public boolean matches(List values) { + for (String value : values) { + if (terms.contains(value)) { + return true; + } + } + return false; + } + + @Override + public void visit(QueryVisitor visitor) { + if (visitor.acceptField(fieldName())) { + for (String term : terms) { + visitor.consumeTerms(this, new Term(fieldName(), term)); + } + } + } + + @Override + public final String toString(String field) { + if (fieldName().contentEquals(field)) { + return terms.toString(); + } + return fieldName() + ":" + terms; + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), terms); + } + + @Override + public boolean equals(Object obj) { + if (false == super.equals(obj)) { + return false; + } + StringScriptFieldTermsQuery other = (StringScriptFieldTermsQuery) obj; + return terms.equals(other.terms); + } + + Set terms() { + return terms; + } +} 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 index 2bd42c2430098..8fdc9188726e1 100644 --- 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 @@ -91,6 +91,17 @@ public void collect(int doc) throws IOException { } } + public void testExistsQuery() 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\": []}")))); + try (DirectoryReader reader = iw.getReader()) { + IndexSearcher searcher = newSearcher(reader); + assertThat(searcher.count(build("for (def v : source.foo) { value(v.toString())}").existsQuery(mockContext())), equalTo(1)); + } + } + } + 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}")))); @@ -102,6 +113,19 @@ public void testTermQuery() throws IOException { } } + public void testTermsQuery() 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}")))); + iw.addDocument(List.of(new StoredField("_source", new BytesRef("{\"foo\": 3}")))); + iw.addDocument(List.of(new StoredField("_source", new BytesRef("{\"foo\": 4}")))); + try (DirectoryReader reader = iw.getReader()) { + IndexSearcher searcher = newSearcher(reader); + assertThat(searcher.count(build("value(source.foo.toString())").termsQuery(List.of("1", "2"), mockContext())), equalTo(2)); + } + } + } + private RuntimeKeywordMappedFieldType build(String code) throws IOException { Script script = new Script(code); PainlessPlugin painlessPlugin = new PainlessPlugin(); diff --git a/x-pack/plugin/runtime-fields/src/test/java/org/elasticsearch/xpack/runtimefields/query/AbstractStringScriptFieldQueryTestCase.java b/x-pack/plugin/runtime-fields/src/test/java/org/elasticsearch/xpack/runtimefields/query/AbstractStringScriptFieldQueryTestCase.java new file mode 100644 index 0000000000000..2f3edad1142c4 --- /dev/null +++ b/x-pack/plugin/runtime-fields/src/test/java/org/elasticsearch/xpack/runtimefields/query/AbstractStringScriptFieldQueryTestCase.java @@ -0,0 +1,37 @@ +/* + * 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.elasticsearch.test.ESTestCase; +import org.elasticsearch.test.EqualsHashCodeTestUtils; +import org.elasticsearch.xpack.runtimefields.StringScriptFieldScript; + +import static org.hamcrest.Matchers.equalTo; +import static org.mockito.Mockito.mock; + +public abstract class AbstractStringScriptFieldQueryTestCase extends ESTestCase { + protected final StringScriptFieldScript.LeafFactory leafFactory = mock(StringScriptFieldScript.LeafFactory.class); + + protected abstract T createTestInstance(); + + protected abstract T copy(T orig); + + protected abstract T mutate(T orig); + + public final void testEqualsAndHashCode() { + EqualsHashCodeTestUtils.checkEqualsAndHashCode(createTestInstance(), this::copy, this::mutate); + } + + public final void testToString() { + T query = createTestInstance(); + assertThat(query.toString(), equalTo(query.fieldName() + ":" + query.toString(query.fieldName()))); + } + + protected abstract void assertToString(T query); + + public abstract void testVisit(); +} diff --git a/x-pack/plugin/runtime-fields/src/test/java/org/elasticsearch/xpack/runtimefields/query/StringScriptFieldExistsQueryTests.java b/x-pack/plugin/runtime-fields/src/test/java/org/elasticsearch/xpack/runtimefields/query/StringScriptFieldExistsQueryTests.java new file mode 100644 index 0000000000000..9eda9a6d84020 --- /dev/null +++ b/x-pack/plugin/runtime-fields/src/test/java/org/elasticsearch/xpack/runtimefields/query/StringScriptFieldExistsQueryTests.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.index.Term; +import org.apache.lucene.search.Query; +import org.apache.lucene.search.QueryVisitor; +import org.apache.lucene.util.automaton.ByteRunAutomaton; + +import java.util.function.Supplier; + +import static org.hamcrest.Matchers.equalTo; + +public class StringScriptFieldExistsQueryTests extends AbstractStringScriptFieldQueryTestCase { + @Override + protected StringScriptFieldExistsQuery createTestInstance() { + return new StringScriptFieldExistsQuery(leafFactory, randomAlphaOfLength(5)); + } + + @Override + protected StringScriptFieldExistsQuery copy(StringScriptFieldExistsQuery orig) { + return new StringScriptFieldExistsQuery(leafFactory, orig.fieldName()); + } + + @Override + protected StringScriptFieldExistsQuery mutate(StringScriptFieldExistsQuery orig) { + return new StringScriptFieldExistsQuery(leafFactory, orig.fieldName() + "modified"); + } + + @Override + protected void assertToString(StringScriptFieldExistsQuery query) { + assertThat(query.toString(query.fieldName()), equalTo("*")); + } + + @Override + public void testVisit() { + createTestInstance().visit(new QueryVisitor() { + @Override + public void consumeTerms(Query query, Term... terms) { + fail(); + } + + @Override + public void consumeTermsMatching(Query query, String field, Supplier automaton) { + fail(); + } + }); + } +} diff --git a/x-pack/plugin/runtime-fields/src/test/java/org/elasticsearch/xpack/runtimefields/query/StringScriptFieldTermQueryTests.java b/x-pack/plugin/runtime-fields/src/test/java/org/elasticsearch/xpack/runtimefields/query/StringScriptFieldTermQueryTests.java new file mode 100644 index 0000000000000..d6e444b0923b3 --- /dev/null +++ b/x-pack/plugin/runtime-fields/src/test/java/org/elasticsearch/xpack/runtimefields/query/StringScriptFieldTermQueryTests.java @@ -0,0 +1,62 @@ +/* + * 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.Term; +import org.apache.lucene.search.Query; +import org.apache.lucene.search.QueryVisitor; +import org.apache.lucene.util.automaton.ByteRunAutomaton; + +import java.util.Arrays; +import java.util.Set; +import java.util.TreeSet; +import java.util.function.Supplier; + +import static org.hamcrest.Matchers.equalTo; + +public class StringScriptFieldTermQueryTests extends AbstractStringScriptFieldQueryTestCase { + @Override + protected StringScriptFieldTermQuery createTestInstance() { + return new StringScriptFieldTermQuery(leafFactory, randomAlphaOfLength(5), randomAlphaOfLength(6)); + } + + @Override + protected StringScriptFieldTermQuery copy(StringScriptFieldTermQuery orig) { + return new StringScriptFieldTermQuery(leafFactory, orig.fieldName(), orig.term()); + } + + @Override + protected StringScriptFieldTermQuery mutate(StringScriptFieldTermQuery orig) { + if (randomBoolean()) { + return new StringScriptFieldTermQuery(leafFactory, orig.fieldName() + "modified", orig.term()); + } + return new StringScriptFieldTermQuery(leafFactory, orig.fieldName(), orig.term() + "modified"); + } + + @Override + protected void assertToString(StringScriptFieldTermQuery query) { + assertThat(query.toString(query.fieldName()), equalTo(query.term())); + } + + @Override + public void testVisit() { + StringScriptFieldTermQuery query = createTestInstance(); + Set allTerms = new TreeSet<>(); + query.visit(new QueryVisitor() { + @Override + public void consumeTerms(Query query, Term... terms) { + allTerms.addAll(Arrays.asList(terms)); + } + + @Override + public void consumeTermsMatching(Query query, String field, Supplier automaton) { + fail(); + } + }); + assertThat(allTerms, equalTo(Set.of(new Term(query.fieldName(), query.term())))); + } +} diff --git a/x-pack/plugin/runtime-fields/src/test/java/org/elasticsearch/xpack/runtimefields/query/StringScriptFieldTermsQueryTests.java b/x-pack/plugin/runtime-fields/src/test/java/org/elasticsearch/xpack/runtimefields/query/StringScriptFieldTermsQueryTests.java new file mode 100644 index 0000000000000..63ec95c559f40 --- /dev/null +++ b/x-pack/plugin/runtime-fields/src/test/java/org/elasticsearch/xpack/runtimefields/query/StringScriptFieldTermsQueryTests.java @@ -0,0 +1,66 @@ +/* + * 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.Term; +import org.apache.lucene.search.Query; +import org.apache.lucene.search.QueryVisitor; +import org.apache.lucene.util.automaton.ByteRunAutomaton; + +import java.util.Arrays; +import java.util.Set; +import java.util.TreeSet; +import java.util.function.Supplier; + +import static java.util.stream.Collectors.toCollection; +import static org.hamcrest.Matchers.equalTo; + +public class StringScriptFieldTermsQueryTests extends AbstractStringScriptFieldQueryTestCase { + @Override + protected StringScriptFieldTermsQuery createTestInstance() { + Set terms = new TreeSet<>(Arrays.asList(generateRandomStringArray(4, 6, false, false))); + return new StringScriptFieldTermsQuery(leafFactory, randomAlphaOfLength(5), terms); + } + + @Override + protected StringScriptFieldTermsQuery copy(StringScriptFieldTermsQuery orig) { + return new StringScriptFieldTermsQuery(leafFactory, orig.fieldName(), orig.terms()); + } + + @Override + protected StringScriptFieldTermsQuery mutate(StringScriptFieldTermsQuery orig) { + if (randomBoolean()) { + return new StringScriptFieldTermsQuery(leafFactory, orig.fieldName() + "modified", orig.terms()); + } + Set terms = new TreeSet<>(orig.terms()); + terms.add(randomAlphaOfLength(7)); + return new StringScriptFieldTermsQuery(leafFactory, orig.fieldName(), terms); + } + + @Override + protected void assertToString(StringScriptFieldTermsQuery query) { + assertThat(query.toString(query.fieldName()), equalTo(query.terms().toString())); + } + + @Override + public void testVisit() { + StringScriptFieldTermsQuery query = createTestInstance(); + Set allTerms = new TreeSet<>(); + query.visit(new QueryVisitor() { + @Override + public void consumeTerms(Query query, Term... terms) { + allTerms.addAll(Arrays.asList(terms)); + } + + @Override + public void consumeTermsMatching(Query query, String field, Supplier automaton) { + fail(); + } + }); + assertThat(allTerms, equalTo(query.terms().stream().map(t -> new Term(query.fieldName(), t)).collect(toCollection(TreeSet::new)))); + } +} 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 241c0d07f6322..e66f8d9b6a32a 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 @@ -21,6 +21,14 @@ setup: type: script runtime_type: keyword script: value(doc['timestamp'].value.dayOfWeekEnum.getDisplayName(TextStyle.FULL, Locale.ROOT)) + days_starting_with_t: + type: script + runtime_type: keyword + script: | + String dow = doc['timestamp'].value.dayOfWeekEnum.getDisplayName(TextStyle.FULL, Locale.ROOT); + if (dow.startsWith('T')) { + value(dow); + } - do: bulk: @@ -67,6 +75,20 @@ setup: - match: {aggregations.dow.buckets.1.key: Monday} - match: {aggregations.dow.buckets.1.doc_count: 1} +--- +"exists query": + - do: + search: + index: sensor + body: + query: + exists: + field: days_starting_with_t + sort: timestamp + - match: {hits.total.value: 2} + - match: {hits.hits.0._source.voltage: 4.0} + - match: {hits.hits.1._source.voltage: 5.2} + --- "term query": - do: @@ -78,3 +100,17 @@ setup: day_of_week: Monday - match: {hits.total.value: 1} - match: {hits.hits.0._source.voltage: 5.8} + +--- +"terms query": + - do: + search: + index: sensor + body: + query: + terms: + day_of_week: [Monday, Tuesday] + sort: timestamp + - match: {hits.total.value: 2} + - match: {hits.hits.0._source.voltage: 5.8} + - match: {hits.hits.1._source.voltage: 5.2}