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 18d36697c29a0..fa39ab02bbb4a 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 @@ -9,6 +9,7 @@ import org.apache.lucene.search.MultiTermQuery.RewriteMethod; import org.apache.lucene.search.Query; import org.apache.lucene.util.BytesRef; +import org.elasticsearch.ElasticsearchException; import org.elasticsearch.common.geo.ShapeRelation; import org.elasticsearch.common.lucene.BytesRefs; import org.elasticsearch.common.time.DateMathParser; @@ -38,6 +39,7 @@ import java.util.Set; import static java.util.stream.Collectors.toSet; +import static org.elasticsearch.search.SearchService.ALLOW_EXPENSIVE_QUERIES; public final class RuntimeKeywordMappedFieldType extends MappedFieldType { @@ -77,8 +79,21 @@ private StringScriptFieldScript.LeafFactory leafFactory(QueryShardContext contex return scriptFactory.newFactory(script.getParams(), context.lookup()); } + private void checkAllowExpensiveQueries(QueryShardContext context) { + if (context.allowExpensiveQueries() == false) { + throw new ElasticsearchException( + "queries cannot be executed against [" + + ScriptFieldMapper.CONTENT_TYPE + + "] fields while [" + + ALLOW_EXPENSIVE_QUERIES.getKey() + + "] is set to [false]." + ); + } + } + @Override public Query existsQuery(QueryShardContext context) { + checkAllowExpensiveQueries(context); return new StringScriptFieldExistsQuery(script, leafFactory(context), name()); } @@ -91,6 +106,7 @@ public Query fuzzyQuery( boolean transpositions, QueryShardContext context ) { + checkAllowExpensiveQueries(context); return StringScriptFieldFuzzyQuery.build( script, leafFactory(context), @@ -104,6 +120,7 @@ public Query fuzzyQuery( @Override public Query prefixQuery(String value, RewriteMethod method, org.elasticsearch.index.query.QueryShardContext context) { + checkAllowExpensiveQueries(context); return new StringScriptFieldPrefixQuery(script, leafFactory(context), name(), value); } @@ -118,6 +135,7 @@ public Query rangeQuery( DateMathParser parser, QueryShardContext context ) { + checkAllowExpensiveQueries(context); return new StringScriptFieldRangeQuery( script, leafFactory(context), @@ -131,22 +149,26 @@ public Query rangeQuery( @Override public Query regexpQuery(String value, int flags, int maxDeterminizedStates, RewriteMethod method, QueryShardContext context) { + checkAllowExpensiveQueries(context); return new StringScriptFieldRegexpQuery(script, leafFactory(context), name(), value, flags, maxDeterminizedStates); } @Override public Query termQuery(Object value, QueryShardContext context) { + checkAllowExpensiveQueries(context); return new StringScriptFieldTermQuery(script, leafFactory(context), name(), BytesRefs.toString(Objects.requireNonNull(value))); } @Override public Query termsQuery(List values, QueryShardContext context) { + checkAllowExpensiveQueries(context); Set terms = values.stream().map(v -> BytesRefs.toString(Objects.requireNonNull(v))).collect(toSet()); return new StringScriptFieldTermsQuery(script, leafFactory(context), name(), terms); } @Override public Query wildcardQuery(String value, RewriteMethod method, QueryShardContext context) { + checkAllowExpensiveQueries(context); return new StringScriptFieldWildcardQuery(script, leafFactory(context), name(), value); } 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 dcc75356fcb61..c5baa0b7ff4d9 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 @@ -19,6 +19,7 @@ import org.apache.lucene.store.Directory; import org.apache.lucene.util.BytesRef; import org.apache.lucene.util.automaton.Operations; +import org.elasticsearch.ElasticsearchException; import org.elasticsearch.Version; import org.elasticsearch.cluster.metadata.IndexMetadata; import org.elasticsearch.common.settings.Settings; @@ -42,6 +43,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.List; +import java.util.function.BiConsumer; import static java.util.Collections.emptyMap; import static org.hamcrest.Matchers.equalTo; @@ -104,6 +106,10 @@ public void testExistsQuery() throws IOException { } } + public void testExistsQueryIsExpensive() throws IOException { + checkExpensiveQuery(RuntimeKeywordMappedFieldType::existsQuery); + } + public void testFuzzyQuery() throws IOException { try (Directory directory = newDirectory(); RandomIndexWriter iw = new RandomIndexWriter(random(), directory)) { iw.addDocument(List.of(new StoredField("_source", new BytesRef("{\"foo\": \"cat\"}")))); // No edits, matches @@ -121,6 +127,19 @@ public void testFuzzyQuery() throws IOException { } } + public void testFuzzyQueryIsExpensive() throws IOException { + checkExpensiveQuery( + (ft, ctx) -> ft.fuzzyQuery( + randomAlphaOfLengthBetween(1, 1000), + randomFrom(Fuzziness.AUTO, Fuzziness.ZERO, Fuzziness.ONE, Fuzziness.TWO), + randomInt(), + randomInt(), + randomBoolean(), + ctx + ) + ); + } + public void testPrefixQuery() throws IOException { try (Directory directory = newDirectory(); RandomIndexWriter iw = new RandomIndexWriter(random(), directory)) { iw.addDocument(List.of(new StoredField("_source", new BytesRef("{\"foo\": \"cat\"}")))); @@ -133,6 +152,10 @@ public void testPrefixQuery() throws IOException { } } + public void testPrefixQueryIsExpensive() throws IOException { + checkExpensiveQuery((ft, ctx) -> ft.prefixQuery(randomAlphaOfLengthBetween(1, 1000), null, ctx)); + } + public void testRangeQuery() throws IOException { try (Directory directory = newDirectory(); RandomIndexWriter iw = new RandomIndexWriter(random(), directory)) { iw.addDocument(List.of(new StoredField("_source", new BytesRef("{\"foo\": \"cat\"}")))); @@ -148,6 +171,21 @@ public void testRangeQuery() throws IOException { } } + public void testRangeQueryIsExpensive() throws IOException { + checkExpensiveQuery( + (ft, ctx) -> ft.rangeQuery( + "a" + randomAlphaOfLengthBetween(0, 1000), + "b" + randomAlphaOfLengthBetween(0, 1000), + randomBoolean(), + randomBoolean(), + null, + null, + null, + ctx + ) + ); + } + public void testRegexpQuery() throws IOException { try (Directory directory = newDirectory(); RandomIndexWriter iw = new RandomIndexWriter(random(), directory)) { iw.addDocument(List.of(new StoredField("_source", new BytesRef("{\"foo\": \"cat\"}")))); @@ -165,6 +203,10 @@ public void testRegexpQuery() throws IOException { } } + public void testRegexpQueryIsExpensive() throws IOException { + checkExpensiveQuery((ft, ctx) -> ft.regexpQuery(randomAlphaOfLengthBetween(1, 1000), randomInt(0xFFFF), randomInt(), null, ctx)); + } + 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}")))); @@ -176,6 +218,10 @@ public void testTermQuery() throws IOException { } } + public void testTermQueryIsExpensive() throws IOException { + checkExpensiveQuery((ft, ctx) -> ft.termQuery(randomAlphaOfLengthBetween(1, 1000), ctx)); + } + 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}")))); @@ -189,6 +235,10 @@ public void testTermsQuery() throws IOException { } } + public void testTermsQueryIsExpensive() throws IOException { + checkExpensiveQuery((ft, ctx) -> ft.termsQuery(randomList(100, () -> randomAlphaOfLengthBetween(1, 1000)), ctx)); + } + public void testWildcardQuery() throws IOException { try (Directory directory = newDirectory(); RandomIndexWriter iw = new RandomIndexWriter(random(), directory)) { iw.addDocument(List.of(new StoredField("_source", new BytesRef("{\"foo\": \"aab\"}")))); @@ -200,6 +250,10 @@ public void testWildcardQuery() throws IOException { } } + public void testWildcardQueryIsExpensive() throws IOException { + checkExpensiveQuery((ft, ctx) -> ft.wildcardQuery(randomAlphaOfLengthBetween(1, 1000), null, ctx)); + } + private RuntimeKeywordMappedFieldType build(String code) throws IOException { Script script = new Script(code); PainlessPlugin painlessPlugin = new PainlessPlugin(); @@ -218,9 +272,23 @@ public List loadExtensions(Class extensionPointType) { } private QueryShardContext mockContext() { + return mockContext(true); + } + + private QueryShardContext mockContext(boolean allowExpensiveQueries) { MapperService mapperService = mock(MapperService.class); QueryShardContext context = mock(QueryShardContext.class); + when(context.allowExpensiveQueries()).thenReturn(allowExpensiveQueries); when(context.lookup()).thenReturn(new SearchLookup(mapperService, mft -> null)); return context; } + + private void checkExpensiveQuery(BiConsumer queryBuilder) throws IOException { + RuntimeKeywordMappedFieldType ft = build("value('cat')"); + Exception e = expectThrows(ElasticsearchException.class, () -> queryBuilder.accept(ft, mockContext(false))); + assertThat( + e.getMessage(), + equalTo("queries cannot be executed against [script] fields while [search.allow_expensive_queries] is set to [false].") + ); + } }