From d9a14a6e90d1a4c65910e287c9c2984ef96ef5a2 Mon Sep 17 00:00:00 2001 From: Marios Trivyzas Date: Fri, 24 Jan 2020 17:59:29 +0100 Subject: [PATCH] Add a cluster setting to disallow slow queries Add a new cluster setting `search.disallow_slow_queries` which by default is `false`. If set to `true` then certain queries (prefix, fuzzy, regexp and wildcard) that have usually slow performance cannot be executed and an exception is thrown. Closes: #29050 --- docs/reference/query-dsl.asciidoc | 10 +- docs/reference/query-dsl/fuzzy-query.asciidoc | 6 +- .../reference/query-dsl/prefix-query.asciidoc | 10 +- .../query-dsl/query-string-query.asciidoc | 6 + .../reference/query-dsl/regexp-query.asciidoc | 5 + .../query-dsl/wildcard-query.asciidoc | 7 +- .../mapper/SearchAsYouTypeFieldTypeTests.java | 10 +- .../ICUCollationKeywordFieldMapper.java | 2 +- .../index/mapper/CollationFieldTypeTests.java | 8 +- .../test/search/320_disallow_queries.yml | 213 ++++++++++++++++++ .../common/settings/ClusterSettings.java | 1 + .../org/elasticsearch/index/IndexModule.java | 16 +- .../org/elasticsearch/index/IndexService.java | 11 +- .../index/mapper/MappedFieldType.java | 5 +- .../index/mapper/StringFieldType.java | 22 +- .../index/query/FuzzyQueryBuilder.java | 2 +- .../index/query/QueryShardContext.java | 36 ++- .../index/search/MatchQuery.java | 3 +- .../index/search/QueryStringQueryParser.java | 2 +- .../search/SimpleQueryStringQueryParser.java | 6 +- .../elasticsearch/indices/IndicesService.java | 15 +- .../elasticsearch/search/SearchService.java | 2 + .../index/mapper/IgnoredFieldTypeTests.java | 22 +- .../index/mapper/KeywordFieldTypeTests.java | 20 +- .../index/mapper/RoutingFieldTypeTests.java | 23 +- .../index/mapper/TextFieldTypeTests.java | 32 ++- .../index/mapper/FieldTypeTestCase.java | 15 ++ .../mapper/FlatObjectFieldMapper.java | 2 +- .../mapper/KeyedFlatObjectFieldTypeTests.java | 14 +- .../mapper/RootFlatObjectFieldTypeTests.java | 23 +- .../xpack/watcher/WatcherPluginTests.java | 3 +- 31 files changed, 496 insertions(+), 56 deletions(-) create mode 100644 rest-api-spec/src/main/resources/rest-api-spec/test/search/320_disallow_queries.yml diff --git a/docs/reference/query-dsl.asciidoc b/docs/reference/query-dsl.asciidoc index 58ebe3190a352..cbc016b8fb8f4 100644 --- a/docs/reference/query-dsl.asciidoc +++ b/docs/reference/query-dsl.asciidoc @@ -25,6 +25,14 @@ or to alter their behaviour (such as the Query clauses behave differently depending on whether they are used in <>. + +[[query-dsl-disallow-slow]] +== Disallow slow queries +Certain queries like <>, <>, +<> and <> , +that are usually slow performance can affect the cluster performance. +The execution of such queries can be prevented by setting the value of the `search.disallow_slow_queries` +setting to `true` (defaults tp `false`). -- include::query-dsl/query_filter_context.asciidoc[] @@ -51,4 +59,4 @@ include::query-dsl/minimum-should-match.asciidoc[] include::query-dsl/multi-term-rewrite.asciidoc[] -include::query-dsl/regexp-syntax.asciidoc[] \ No newline at end of file +include::query-dsl/regexp-syntax.asciidoc[] diff --git a/docs/reference/query-dsl/fuzzy-query.asciidoc b/docs/reference/query-dsl/fuzzy-query.asciidoc index bb20e0bd7e720..e145b8a3c81d8 100644 --- a/docs/reference/query-dsl/fuzzy-query.asciidoc +++ b/docs/reference/query-dsl/fuzzy-query.asciidoc @@ -97,4 +97,8 @@ adjacent characters (ab → ba). Defaults to `true`. `rewrite`:: (Optional, string) Method used to rewrite the query. For valid values and more -information, see the <>. \ No newline at end of file +information, see the <>. + +==== Notes +Fuzzy queries will not be executed if <> +is set to true. diff --git a/docs/reference/query-dsl/prefix-query.asciidoc b/docs/reference/query-dsl/prefix-query.asciidoc index 780de433aabc0..e6802c428c257 100644 --- a/docs/reference/query-dsl/prefix-query.asciidoc +++ b/docs/reference/query-dsl/prefix-query.asciidoc @@ -64,4 +64,12 @@ GET /_search You can speed up prefix queries using the <> mapping parameter. If enabled, {es} indexes prefixes between 2 and 5 characters in a separate field. This lets {es} run prefix queries more -efficiently at the cost of a larger index. \ No newline at end of file +efficiently at the cost of a larger index. + +[[prefix-query-disallow-slow]] +===== Disallow slow queries +Prefix queries will not be executed if <> +is set to true. If <> are enabled though, an optimised query is +built, which is not considered slow and is executed despite of the fact that the +<> set to true. + diff --git a/docs/reference/query-dsl/query-string-query.asciidoc b/docs/reference/query-dsl/query-string-query.asciidoc index 56eb3b6efb5e5..f1b07c680f0e4 100644 --- a/docs/reference/query-dsl/query-string-query.asciidoc +++ b/docs/reference/query-dsl/query-string-query.asciidoc @@ -537,3 +537,9 @@ The example above creates a boolean query: `(blended(terms:[field2:this, field1:this]) blended(terms:[field2:that, field1:that]) blended(terms:[field2:thus, field1:thus]))~2` that matches documents with at least two of the three per-term blended queries. + +==== Notes +===== Disallow slow queries +Query string query can be internally be transformed to a <> which means +that if the prefix queries are disabled as explained <> the query will not be +executed and an exception will be thrown. diff --git a/docs/reference/query-dsl/regexp-query.asciidoc b/docs/reference/query-dsl/regexp-query.asciidoc index e92424afbc2a5..329259d008ba5 100644 --- a/docs/reference/query-dsl/regexp-query.asciidoc +++ b/docs/reference/query-dsl/regexp-query.asciidoc @@ -86,3 +86,8 @@ regular expressions. `rewrite`:: (Optional, string) Method used to rewrite the query. For valid values and more information, see the <>. + +==== Notes +===== Disallow slow queries +Regexp queries will not be executed if <> +is set to true. diff --git a/docs/reference/query-dsl/wildcard-query.asciidoc b/docs/reference/query-dsl/wildcard-query.asciidoc index 5cc1dacfb6efb..604f19692e4c7 100644 --- a/docs/reference/query-dsl/wildcard-query.asciidoc +++ b/docs/reference/query-dsl/wildcard-query.asciidoc @@ -67,4 +67,9 @@ increases the relevance score. `rewrite`:: (Optional, string) Method used to rewrite the query. For valid values and more information, see the -<>. \ No newline at end of file +<>. + +==== Notes +===== Disallow slow queries +Wildcard queries will not be executed if <> +is set to true. diff --git a/modules/mapper-extras/src/test/java/org/elasticsearch/index/mapper/SearchAsYouTypeFieldTypeTests.java b/modules/mapper-extras/src/test/java/org/elasticsearch/index/mapper/SearchAsYouTypeFieldTypeTests.java index 523de91809145..609b3144fa826 100644 --- a/modules/mapper-extras/src/test/java/org/elasticsearch/index/mapper/SearchAsYouTypeFieldTypeTests.java +++ b/modules/mapper-extras/src/test/java/org/elasticsearch/index/mapper/SearchAsYouTypeFieldTypeTests.java @@ -26,6 +26,7 @@ import org.apache.lucene.search.TermInSetQuery; import org.apache.lucene.search.TermQuery; import org.apache.lucene.util.BytesRef; +import org.elasticsearch.ElasticsearchException; import org.elasticsearch.index.mapper.SearchAsYouTypeFieldMapper.Defaults; import org.elasticsearch.index.mapper.SearchAsYouTypeFieldMapper.PrefixFieldType; import org.elasticsearch.index.mapper.SearchAsYouTypeFieldMapper.SearchAsYouTypeFieldType; @@ -100,14 +101,19 @@ public void testPrefixQuery() { // this term should be a length that can be rewriteable to a term query on the prefix field final String withinBoundsTerm = "foo"; - assertThat(fieldType.prefixQuery(withinBoundsTerm, CONSTANT_SCORE_REWRITE, null), + assertThat(fieldType.prefixQuery(withinBoundsTerm, CONSTANT_SCORE_REWRITE, randomMockShardContext()), equalTo(new ConstantScoreQuery(new TermQuery(new Term(PREFIX_NAME, withinBoundsTerm))))); // our defaults don't allow a situation where a term can be too small // this term should be too long to be rewriteable to a term query on the prefix field final String longTerm = "toolongforourprefixfieldthistermis"; - assertThat(fieldType.prefixQuery(longTerm, CONSTANT_SCORE_REWRITE, null), + assertThat(fieldType.prefixQuery(longTerm, CONSTANT_SCORE_REWRITE, MOCK_QSC), equalTo(new PrefixQuery(new Term(NAME, longTerm)))); + + ElasticsearchException ee = expectThrows(ElasticsearchException.class, + () -> fieldType.prefixQuery(longTerm, CONSTANT_SCORE_REWRITE, MOCK_QSC_DISALLOW_SLOW)); + assertEquals("prefix queries cannot be executed when 'search.disallow_slow_queries' is set to true", + ee.getMessage()); } } diff --git a/plugins/analysis-icu/src/main/java/org/elasticsearch/index/mapper/ICUCollationKeywordFieldMapper.java b/plugins/analysis-icu/src/main/java/org/elasticsearch/index/mapper/ICUCollationKeywordFieldMapper.java index 4b29d314356df..883468941a5d8 100644 --- a/plugins/analysis-icu/src/main/java/org/elasticsearch/index/mapper/ICUCollationKeywordFieldMapper.java +++ b/plugins/analysis-icu/src/main/java/org/elasticsearch/index/mapper/ICUCollationKeywordFieldMapper.java @@ -158,7 +158,7 @@ protected BytesRef indexedValueForSearch(Object value) { @Override public Query fuzzyQuery(Object value, Fuzziness fuzziness, int prefixLength, int maxExpansions, - boolean transpositions) { + boolean transpositions, QueryShardContext context) { throw new UnsupportedOperationException("[fuzzy] queries are not supported on [" + CONTENT_TYPE + "] fields."); } diff --git a/plugins/analysis-icu/src/test/java/org/elasticsearch/index/mapper/CollationFieldTypeTests.java b/plugins/analysis-icu/src/test/java/org/elasticsearch/index/mapper/CollationFieldTypeTests.java index a261e8b3b7e9a..df82de52b140b 100644 --- a/plugins/analysis-icu/src/test/java/org/elasticsearch/index/mapper/CollationFieldTypeTests.java +++ b/plugins/analysis-icu/src/test/java/org/elasticsearch/index/mapper/CollationFieldTypeTests.java @@ -102,7 +102,7 @@ public void testRegexpQuery() { ft.setName("field"); ft.setIndexOptions(IndexOptions.DOCS); expectThrows(UnsupportedOperationException.class, - () -> ft.regexpQuery("foo.*", 0, 10, null, null)); + () -> ft.regexpQuery("foo.*", 0, 10, null, randomMockShardContext())); } public void testFuzzyQuery() { @@ -110,7 +110,7 @@ public void testFuzzyQuery() { ft.setName("field"); ft.setIndexOptions(IndexOptions.DOCS); expectThrows(UnsupportedOperationException.class, - () -> ft.fuzzyQuery("foo", Fuzziness.fromEdits(2), 1, 50, true)); + () -> ft.fuzzyQuery("foo", Fuzziness.fromEdits(2), 1, 50, true, randomMockShardContext())); } public void testPrefixQuery() { @@ -118,7 +118,7 @@ public void testPrefixQuery() { ft.setName("field"); ft.setIndexOptions(IndexOptions.DOCS); expectThrows(UnsupportedOperationException.class, - () -> ft.prefixQuery("prefix", null, null)); + () -> ft.prefixQuery("prefix", null, randomMockShardContext())); } public void testWildcardQuery() { @@ -126,7 +126,7 @@ public void testWildcardQuery() { ft.setName("field"); ft.setIndexOptions(IndexOptions.DOCS); expectThrows(UnsupportedOperationException.class, - () -> ft.wildcardQuery("foo*", null, null)); + () -> ft.wildcardQuery("foo*", null, randomMockShardContext())); } public void testRangeQuery() { diff --git a/rest-api-spec/src/main/resources/rest-api-spec/test/search/320_disallow_queries.yml b/rest-api-spec/src/main/resources/rest-api-spec/test/search/320_disallow_queries.yml new file mode 100644 index 0000000000000..04c674bdababf --- /dev/null +++ b/rest-api-spec/src/main/resources/rest-api-spec/test/search/320_disallow_queries.yml @@ -0,0 +1,213 @@ +--- +setup: + - skip: + version: " - 7.99.99" + reason: "implemented in 8.0.0" + + - do: + indices.create: + index: test + body: + mappings: + properties: + text: + type: text + analyzer: standard + - do: + bulk: + refresh: true + body: + - '{"index": {"_index": "test", "_id": "1"}}' + - '{"text" : "Some like it hot, some like it cold"}' + - '{"index": {"_index": "test", "_id": "2"}}' + - '{"text" : "Its cold outside, theres no kind of atmosphere"}' + - '{"index": {"_index": "test", "_id": "3"}}' + - '{"text" : "Baby its cold there outside"}' + - '{"index": {"_index": "test", "_id": "4"}}' + - '{"text" : "Outside it is cold and wet"}' + +--- +teardown: + - skip: + version: " - 7.99.99" + reason: "implemented in 8.0.0" + + - do: + cluster.put_settings: + body: + transient: + search.disallow_slow_queries: null + +--- +"Test disallow slow queries": + - skip: + version: " - 7.99.99" + reason: "implemented in 8.0.0" + + ### Check for initial setting = null -> false + - do: + cluster.get_settings: + flat_settings: true + + - match: {search.disallow_slow_queries: null} + + ### Prefix + - do: + search: + index: test + body: + query: + prefix: + text: + value: out + + - match: { hits.total.value: 3 } + + ### Fuzzy + - do: + search: + index: test + body: + query: + fuzzy: + text: + value: outwide + + - match: { hits.total.value: 3 } + + + ### Regexp + - do: + search: + index: test + body: + query: + regexp: + text: + value: .*ou.*id.* + + - match: { hits.total.value: 3 } + + ### Wildcard + - do: + search: + index: test + body: + query: + wildcard: + text: + value: out?ide + + - match: { hits.total.value: 3 } + + ### Update setting to true + - do: + cluster.put_settings: + body: + transient: + search.disallow_slow_queries: "true" + flat_settings: true + + - match: {transient: {search.disallow_slow_queries: "true"}} + + ### Prefix + - do: + catch: /prefix queries cannot be executed when \'search.disallow_slow_queries\' is set to true/ + search: + index: test + body: + query: + prefix: + text: + value: out + + ### Fuzzy + - do: + catch: /fuzzy queries cannot be executed when \'search.disallow_slow_queries\' is set to true/ + search: + index: test + body: + query: + fuzzy: + text: + value: outwide + + ### Regexp + - do: + catch: /regexp queries cannot be executed when \'search.disallow_slow_queries\' is set to true/ + search: + index: test + body: + query: + regexp: + text: + value: .*ou.*id.* + + ### Wildcard + - do: + catch: /wildcard queries cannot be executed when \'search.disallow_slow_queries\' is set to true/ + search: + index: test + body: + query: + wildcard: + text: + value: out?ide + + ### Revert setting to false + - do: + cluster.put_settings: + body: + transient: + search.disallow_slow_queries: "false" + flat_settings: true + + - match: {transient: {search.disallow_slow_queries: "false"}} + + ### Prefix + - do: + search: + index: test + body: + query: + prefix: + text: + value: out + + - match: { hits.total.value: 3 } + + ### Fuzzy + - do: + search: + index: test + body: + query: + fuzzy: + text: + value: outwide + + - match: { hits.total.value: 3 } + + ### Regexp + - do: + search: + index: test + body: + query: + regexp: + text: + value: .*ou.*id.* + + - match: { hits.total.value: 3 } + + ### Wildcard + - do: + search: + index: test + body: + query: + wildcard: + text: + value: out?ide + + - match: { hits.total.value: 3 } diff --git a/server/src/main/java/org/elasticsearch/common/settings/ClusterSettings.java b/server/src/main/java/org/elasticsearch/common/settings/ClusterSettings.java index fcf0c0fe4e61b..88eb4538cb983 100644 --- a/server/src/main/java/org/elasticsearch/common/settings/ClusterSettings.java +++ b/server/src/main/java/org/elasticsearch/common/settings/ClusterSettings.java @@ -388,6 +388,7 @@ public void apply(Settings value, Settings current, Settings previous) { SearchService.DEFAULT_KEEPALIVE_SETTING, SearchService.KEEPALIVE_INTERVAL_SETTING, SearchService.MAX_KEEPALIVE_SETTING, + SearchService.DISALLOW_SLOW_QUERIES, MultiBucketConsumerService.MAX_BUCKET_SETTING, SearchService.LOW_LEVEL_CANCELLATION_SETTING, SearchService.MAX_OPEN_SCROLL_CONTEXT, diff --git a/server/src/main/java/org/elasticsearch/index/IndexModule.java b/server/src/main/java/org/elasticsearch/index/IndexModule.java index f1ce91b212b43..6e10316e0ff96 100644 --- a/server/src/main/java/org/elasticsearch/index/IndexModule.java +++ b/server/src/main/java/org/elasticsearch/index/IndexModule.java @@ -130,6 +130,7 @@ public final class IndexModule { private final List searchOperationListeners = new ArrayList<>(); private final List indexOperationListeners = new ArrayList<>(); private final AtomicBoolean frozen = new AtomicBoolean(false); + private final BooleanSupplier isDisallowSlowQueries; /** * Construct the index module for the index with the specified index settings. The index module contains extension points for plugins @@ -144,13 +145,24 @@ public IndexModule( final IndexSettings indexSettings, final AnalysisRegistry analysisRegistry, final EngineFactory engineFactory, - final Map directoryFactories) { + final Map directoryFactories, + final BooleanSupplier isDisallowSlowQueries) { this.indexSettings = indexSettings; this.analysisRegistry = analysisRegistry; this.engineFactory = Objects.requireNonNull(engineFactory); this.searchOperationListeners.add(new SearchSlowLog(indexSettings)); this.indexOperationListeners.add(new IndexingSlowLog(indexSettings)); this.directoryFactories = Collections.unmodifiableMap(directoryFactories); + this.isDisallowSlowQueries = isDisallowSlowQueries; + } + + // For testing + IndexModule( + final IndexSettings indexSettings, + final AnalysisRegistry analysisRegistry, + final EngineFactory engineFactory, + final Map directoryFactories) { + this(indexSettings, analysisRegistry, engineFactory, directoryFactories, () -> false); } /** @@ -424,7 +436,7 @@ public IndexService newIndexService( new SimilarityService(indexSettings, scriptService, similarities), shardStoreDeleter, indexAnalyzers, engineFactory, circuitBreakerService, bigArrays, threadPool, scriptService, clusterService, client, queryCache, directoryFactory, eventListener, readerWrapperFactory, mapperRegistry, indicesFieldDataCache, searchOperationListeners, - indexOperationListeners, namedWriteableRegistry, idFieldDataEnabled); + indexOperationListeners, namedWriteableRegistry, idFieldDataEnabled, isDisallowSlowQueries); success = true; return indexService; } finally { diff --git a/server/src/main/java/org/elasticsearch/index/IndexService.java b/server/src/main/java/org/elasticsearch/index/IndexService.java index d7cf228e526a7..40db224998f34 100644 --- a/server/src/main/java/org/elasticsearch/index/IndexService.java +++ b/server/src/main/java/org/elasticsearch/index/IndexService.java @@ -59,8 +59,8 @@ import org.elasticsearch.index.fielddata.IndexFieldDataCache; import org.elasticsearch.index.fielddata.IndexFieldDataService; import org.elasticsearch.index.mapper.MapperService; -import org.elasticsearch.index.query.SearchIndexNameMatcher; import org.elasticsearch.index.query.QueryShardContext; +import org.elasticsearch.index.query.SearchIndexNameMatcher; import org.elasticsearch.index.seqno.RetentionLeaseSyncer; import org.elasticsearch.index.shard.IndexEventListener; import org.elasticsearch.index.shard.IndexShard; @@ -126,6 +126,7 @@ public class IndexService extends AbstractIndexComponent implements IndicesClust private final IndexSettings indexSettings; private final List searchOperationListeners; private final List indexingOperationListeners; + private final BooleanSupplier isDisallowSlowQueries; private volatile AsyncRefreshTask refreshTask; private volatile AsyncTranslogFSync fsyncTask; private volatile AsyncGlobalCheckpointTask globalCheckpointTask; @@ -166,8 +167,10 @@ public IndexService( List searchOperationListeners, List indexingOperationListeners, NamedWriteableRegistry namedWriteableRegistry, - BooleanSupplier idFieldDataEnabled) { + BooleanSupplier idFieldDataEnabled, + BooleanSupplier isDisallowSlowQueries) { super(indexSettings); + this.isDisallowSlowQueries = isDisallowSlowQueries; this.indexSettings = indexSettings; this.xContentRegistry = xContentRegistry; this.similarityService = similarityService; @@ -223,6 +226,8 @@ public IndexService( this.globalCheckpointTask = new AsyncGlobalCheckpointTask(this); this.retentionLeaseSyncTask = new AsyncRetentionLeaseSyncTask(this); updateFsyncTaskIfNecessary(); + + } static boolean needsMapperService(IndexSettings indexSettings, IndexCreationContext indexCreationContext) { @@ -568,7 +573,7 @@ public QueryShardContext newQueryShardContext(int shardId, IndexSearcher searche return new QueryShardContext( shardId, indexSettings, bigArrays, indexCache.bitsetFilterCache(), indexFieldData::getForField, mapperService(), similarityService(), scriptService, xContentRegistry, namedWriteableRegistry, client, searcher, nowInMillis, clusterAlias, - indexNameMatcher); + indexNameMatcher, isDisallowSlowQueries); } /** diff --git a/server/src/main/java/org/elasticsearch/index/mapper/MappedFieldType.java b/server/src/main/java/org/elasticsearch/index/mapper/MappedFieldType.java index 86dad273e71b0..e2de09efcb588 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/MappedFieldType.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/MappedFieldType.java @@ -26,6 +26,7 @@ import org.apache.lucene.index.PrefixCodedTerms; import org.apache.lucene.index.PrefixCodedTerms.TermIterator; import org.apache.lucene.index.Term; +import org.apache.lucene.queries.intervals.IntervalsSource; import org.apache.lucene.search.BooleanClause.Occur; import org.apache.lucene.search.BooleanQuery; import org.apache.lucene.search.BoostQuery; @@ -34,7 +35,6 @@ import org.apache.lucene.search.Query; import org.apache.lucene.search.TermInSetQuery; import org.apache.lucene.search.TermQuery; -import org.apache.lucene.queries.intervals.IntervalsSource; import org.apache.lucene.search.spans.SpanMultiTermQueryWrapper; import org.apache.lucene.search.spans.SpanQuery; import org.apache.lucene.util.BytesRef; @@ -350,7 +350,8 @@ public Query rangeQuery( throw new IllegalArgumentException("Field [" + name + "] of type [" + typeName() + "] does not support range queries"); } - public Query fuzzyQuery(Object value, Fuzziness fuzziness, int prefixLength, int maxExpansions, boolean transpositions) { + public Query fuzzyQuery(Object value, Fuzziness fuzziness, int prefixLength, int maxExpansions, boolean transpositions, + QueryShardContext context) { throw new IllegalArgumentException("Can only use fuzzy queries on keyword and text fields - not on [" + name + "] which is of type [" + typeName() + "]"); } diff --git a/server/src/main/java/org/elasticsearch/index/mapper/StringFieldType.java b/server/src/main/java/org/elasticsearch/index/mapper/StringFieldType.java index cde8e392dabb8..e06fe9556df00 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/StringFieldType.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/StringFieldType.java @@ -31,6 +31,7 @@ import org.apache.lucene.search.TermRangeQuery; import org.apache.lucene.search.WildcardQuery; import org.apache.lucene.util.BytesRef; +import org.elasticsearch.ElasticsearchException; import org.elasticsearch.common.lucene.BytesRefs; import org.elasticsearch.common.unit.Fuzziness; import org.elasticsearch.index.query.QueryShardContext; @@ -38,6 +39,8 @@ import java.util.List; +import static org.elasticsearch.search.SearchService.DISALLOW_SLOW_QUERIES; + /** Base class for {@link MappedFieldType} implementations that use the same * representation for internal index terms as the external representation so * that partial matching queries such as prefix, wildcard and fuzzy queries @@ -62,7 +65,11 @@ public Query termsQuery(List values, QueryShardContext context) { @Override public Query fuzzyQuery(Object value, Fuzziness fuzziness, int prefixLength, int maxExpansions, - boolean transpositions) { + boolean transpositions, QueryShardContext context) { + if (context.isDisallowSlowQueries() == true) { + throw new ElasticsearchException("fuzzy queries cannot be executed when '" + DISALLOW_SLOW_QUERIES.getKey() + + "' is set to true"); + } failIfNotIndexed(); return new FuzzyQuery(new Term(name(), indexedValueForSearch(value)), fuzziness.asDistance(BytesRefs.toString(value)), prefixLength, maxExpansions, transpositions); @@ -70,6 +77,10 @@ public Query fuzzyQuery(Object value, Fuzziness fuzziness, int prefixLength, int @Override public Query prefixQuery(String value, MultiTermQuery.RewriteMethod method, QueryShardContext context) { + if (context.isDisallowSlowQueries() == true) { + throw new ElasticsearchException("prefix queries cannot be executed when '" + DISALLOW_SLOW_QUERIES.getKey() + + "' is set to true"); + } failIfNotIndexed(); PrefixQuery query = new PrefixQuery(new Term(name(), indexedValueForSearch(value))); if (method != null) { @@ -84,6 +95,11 @@ public Query wildcardQuery(String value, MultiTermQuery.RewriteMethod method, Qu if (termQuery instanceof MatchNoDocsQuery || termQuery instanceof MatchAllDocsQuery) { return termQuery; } + + if (context.isDisallowSlowQueries() == true) { + throw new ElasticsearchException("wildcard queries cannot be executed when '" + DISALLOW_SLOW_QUERIES.getKey() + + "' is set to true"); + } Term term = MappedFieldType.extractTerm(termQuery); WildcardQuery query = new WildcardQuery(term); @@ -94,6 +110,10 @@ public Query wildcardQuery(String value, MultiTermQuery.RewriteMethod method, Qu @Override public Query regexpQuery(String value, int flags, int maxDeterminizedStates, MultiTermQuery.RewriteMethod method, QueryShardContext context) { + if (context.isDisallowSlowQueries() == true) { + throw new ElasticsearchException("regexp queries cannot be executed when '" + DISALLOW_SLOW_QUERIES.getKey() + + "' is set to true"); + } failIfNotIndexed(); RegexpQuery query = new RegexpQuery(new Term(name(), indexedValueForSearch(value)), flags, maxDeterminizedStates); if (method != null) { diff --git a/server/src/main/java/org/elasticsearch/index/query/FuzzyQueryBuilder.java b/server/src/main/java/org/elasticsearch/index/query/FuzzyQueryBuilder.java index 954107c656086..8df0fec044124 100644 --- a/server/src/main/java/org/elasticsearch/index/query/FuzzyQueryBuilder.java +++ b/server/src/main/java/org/elasticsearch/index/query/FuzzyQueryBuilder.java @@ -328,7 +328,7 @@ protected Query doToQuery(QueryShardContext context) throws IOException { String rewrite = this.rewrite; MappedFieldType fieldType = context.fieldMapper(fieldName); if (fieldType != null) { - query = fieldType.fuzzyQuery(value, fuzziness, prefixLength, maxExpansions, transpositions); + query = fieldType.fuzzyQuery(value, fuzziness, prefixLength, maxExpansions, transpositions, context); } if (query == null) { int maxEdits = fuzziness.asDistance(BytesRefs.toString(value)); diff --git a/server/src/main/java/org/elasticsearch/index/query/QueryShardContext.java b/server/src/main/java/org/elasticsearch/index/query/QueryShardContext.java index 12b2da120f63e..50b8aee218b2a 100644 --- a/server/src/main/java/org/elasticsearch/index/query/QueryShardContext.java +++ b/server/src/main/java/org/elasticsearch/index/query/QueryShardContext.java @@ -66,6 +66,7 @@ import java.util.Set; import java.util.function.BiConsumer; import java.util.function.BiFunction; +import java.util.function.BooleanSupplier; import java.util.function.LongSupplier; import java.util.function.Predicate; @@ -92,12 +93,35 @@ public class QueryShardContext extends QueryRewriteContext { private final Index fullyQualifiedIndex; private final Predicate indexNameMatcher; + private final BooleanSupplier isDisallowSlowQueries; private final Map namedQueries = new HashMap<>(); private boolean allowUnmappedFields; private boolean mapUnmappedFieldAsString; private NestedScope nestedScope; + public QueryShardContext(int shardId, + IndexSettings indexSettings, + BigArrays bigArrays, + BitsetFilterCache bitsetFilterCache, + BiFunction> indexFieldDataLookup, + MapperService mapperService, + SimilarityService similarityService, + ScriptService scriptService, + NamedXContentRegistry xContentRegistry, + NamedWriteableRegistry namedWriteableRegistry, + Client client, + IndexSearcher searcher, + LongSupplier nowInMillis, + String clusterAlias, + Predicate indexNameMatcher, + BooleanSupplier isDisallowSlowQueries) { + this(shardId, indexSettings, bigArrays, bitsetFilterCache, indexFieldDataLookup, mapperService, similarityService, + scriptService, xContentRegistry, namedWriteableRegistry, client, searcher, nowInMillis, indexNameMatcher, + new Index(RemoteClusterAware.buildRemoteIndexName(clusterAlias, indexSettings.getIndex().getName()), + indexSettings.getIndex().getUUID()), isDisallowSlowQueries); + } + public QueryShardContext(int shardId, IndexSettings indexSettings, BigArrays bigArrays, @@ -116,14 +140,14 @@ public QueryShardContext(int shardId, this(shardId, indexSettings, bigArrays, bitsetFilterCache, indexFieldDataLookup, mapperService, similarityService, scriptService, xContentRegistry, namedWriteableRegistry, client, searcher, nowInMillis, indexNameMatcher, new Index(RemoteClusterAware.buildRemoteIndexName(clusterAlias, indexSettings.getIndex().getName()), - indexSettings.getIndex().getUUID())); + indexSettings.getIndex().getUUID()), () -> false); } public QueryShardContext(QueryShardContext source) { this(source.shardId, source.indexSettings, source.bigArrays, source.bitsetFilterCache, source.indexFieldDataService, source.mapperService, source.similarityService, source.scriptService, source.getXContentRegistry(), source.getWriteableRegistry(), source.client, source.searcher, source.nowInMillis, source.indexNameMatcher, - source.fullyQualifiedIndex); + source.fullyQualifiedIndex, source.isDisallowSlowQueries); } private QueryShardContext(int shardId, @@ -140,7 +164,8 @@ private QueryShardContext(int shardId, IndexSearcher searcher, LongSupplier nowInMillis, Predicate indexNameMatcher, - Index fullyQualifiedIndex) { + Index fullyQualifiedIndex, + BooleanSupplier isDisallowSlowQueries) { super(xContentRegistry, namedWriteableRegistry, client, nowInMillis); this.shardId = shardId; this.similarityService = similarityService; @@ -155,6 +180,7 @@ private QueryShardContext(int shardId, this.searcher = searcher; this.indexNameMatcher = indexNameMatcher; this.fullyQualifiedIndex = fullyQualifiedIndex; + this.isDisallowSlowQueries = isDisallowSlowQueries; } private void reset() { @@ -192,6 +218,10 @@ public BitSetProducer bitsetFilter(Query filter) { return bitsetFilterCache.getBitSetProducer(filter); } + public boolean isDisallowSlowQueries() { + return isDisallowSlowQueries.getAsBoolean(); + } + @SuppressWarnings("unchecked") public > IFD getForField(MappedFieldType fieldType) { return (IFD) indexFieldDataService.apply(fieldType, fullyQualifiedIndex.getName()); diff --git a/server/src/main/java/org/elasticsearch/index/search/MatchQuery.java b/server/src/main/java/org/elasticsearch/index/search/MatchQuery.java index d42466b5ebe66..abec51d0039f9 100644 --- a/server/src/main/java/org/elasticsearch/index/search/MatchQuery.java +++ b/server/src/main/java/org/elasticsearch/index/search/MatchQuery.java @@ -525,7 +525,8 @@ protected Query newTermQuery(Term term) { Supplier querySupplier; if (fuzziness != null) { querySupplier = () -> { - Query query = fieldType.fuzzyQuery(term.text(), fuzziness, fuzzyPrefixLength, maxExpansions, transpositions); + Query query = fieldType.fuzzyQuery(term.text(), fuzziness, fuzzyPrefixLength, maxExpansions, + transpositions, context); if (query instanceof FuzzyQuery) { QueryParsers.setRewriteMethod((FuzzyQuery) query, fuzzyRewriteMethod); } diff --git a/server/src/main/java/org/elasticsearch/index/search/QueryStringQueryParser.java b/server/src/main/java/org/elasticsearch/index/search/QueryStringQueryParser.java index 22be2131e3347..0fd2681b0e114 100644 --- a/server/src/main/java/org/elasticsearch/index/search/QueryStringQueryParser.java +++ b/server/src/main/java/org/elasticsearch/index/search/QueryStringQueryParser.java @@ -463,7 +463,7 @@ private Query getFuzzyQuerySingle(String field, String termStr, int minSimilarit Analyzer normalizer = forceAnalyzer == null ? queryBuilder.context.getSearchAnalyzer(currentFieldType) : forceAnalyzer; BytesRef term = termStr == null ? null : normalizer.normalize(field, termStr); return currentFieldType.fuzzyQuery(term, Fuzziness.fromEdits(minSimilarity), - getFuzzyPrefixLength(), fuzzyMaxExpansions, fuzzyTranspositions); + getFuzzyPrefixLength(), fuzzyMaxExpansions, fuzzyTranspositions, context); } catch (RuntimeException e) { if (lenient) { return newLenientFieldQuery(field, e); diff --git a/server/src/main/java/org/elasticsearch/index/search/SimpleQueryStringQueryParser.java b/server/src/main/java/org/elasticsearch/index/search/SimpleQueryStringQueryParser.java index 912e03ca7996c..b8509ca2c112c 100644 --- a/server/src/main/java/org/elasticsearch/index/search/SimpleQueryStringQueryParser.java +++ b/server/src/main/java/org/elasticsearch/index/search/SimpleQueryStringQueryParser.java @@ -42,10 +42,10 @@ import org.elasticsearch.index.query.SimpleQueryStringBuilder; import java.io.IOException; +import java.util.ArrayList; +import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.List; -import java.util.ArrayList; import static org.elasticsearch.common.lucene.search.Queries.newUnmappedFieldQuery; @@ -134,7 +134,7 @@ public Query newFuzzyQuery(String text, int fuzziness) { try { final BytesRef term = getAnalyzer(ft).normalize(fieldName, text); Query query = ft.fuzzyQuery(term, Fuzziness.fromEdits(fuzziness), settings.fuzzyPrefixLength, - settings.fuzzyMaxExpansions, settings.fuzzyTranspositions); + settings.fuzzyMaxExpansions, settings.fuzzyTranspositions, context); disjuncts.add(wrapWithBoost(query, entry.getValue())); } catch (RuntimeException e) { disjuncts.add(rethrowUnlessLenient(e)); diff --git a/server/src/main/java/org/elasticsearch/indices/IndicesService.java b/server/src/main/java/org/elasticsearch/indices/IndicesService.java index 62be919485669..26744f08a4012 100644 --- a/server/src/main/java/org/elasticsearch/indices/IndicesService.java +++ b/server/src/main/java/org/elasticsearch/indices/IndicesService.java @@ -168,6 +168,7 @@ import static org.elasticsearch.index.IndexService.IndexCreationContext.CREATE_INDEX; import static org.elasticsearch.index.IndexService.IndexCreationContext.META_DATA_VERIFICATION; import static org.elasticsearch.index.query.AbstractQueryBuilder.parseInnerQueryBuilder; +import static org.elasticsearch.search.SearchService.DISALLOW_SLOW_QUERIES; public class IndicesService extends AbstractLifecycleComponent implements IndicesClusterStateService.AllocatedIndices, IndexService.ShardStoreDeleter { @@ -220,6 +221,7 @@ public class IndicesService extends AbstractLifecycleComponent final AbstractRefCounted indicesRefCount; // pkg-private for testing private final CountDownLatch closeLatch = new CountDownLatch(1); private volatile boolean idFieldDataEnabled; + private volatile boolean disallowSlowQueries; @Nullable private final EsThreadPoolExecutor danglingIndicesThreadPoolExecutor; @@ -316,6 +318,9 @@ protected void closeInternal() { 0, TimeUnit.MILLISECONDS, daemonThreadFactory(nodeName, DANGLING_INDICES_UPDATE_THREAD_NAME), threadPool.getThreadContext()) : null; + + this.disallowSlowQueries = DISALLOW_SLOW_QUERIES.get(clusterService.getSettings()); + clusterService.getClusterSettings().addSettingsUpdateConsumer(DISALLOW_SLOW_QUERIES, this::setDisallowSlowQueries); } private static final String DANGLING_INDICES_UPDATE_THREAD_NAME = "DanglingIndices#updateTask"; @@ -586,7 +591,8 @@ private synchronized IndexService createIndexService(IndexService.IndexCreationC idxSettings.getNumberOfReplicas(), indexCreationContext); - final IndexModule indexModule = new IndexModule(idxSettings, analysisRegistry, getEngineFactory(idxSettings), directoryFactories); + final IndexModule indexModule = new IndexModule(idxSettings, analysisRegistry, getEngineFactory(idxSettings), + directoryFactories, () -> disallowSlowQueries); for (IndexingOperationListener operationListener : indexingOperationListeners) { indexModule.addIndexOperationListener(operationListener); } @@ -655,7 +661,8 @@ private EngineFactory getEngineFactory(final IndexSettings idxSettings) { */ public synchronized MapperService createIndexMapperService(IndexMetaData indexMetaData) throws IOException { final IndexSettings idxSettings = new IndexSettings(indexMetaData, this.settings, indexScopedSettings); - final IndexModule indexModule = new IndexModule(idxSettings, analysisRegistry, getEngineFactory(idxSettings), directoryFactories); + final IndexModule indexModule = new IndexModule(idxSettings, analysisRegistry, getEngineFactory(idxSettings), + directoryFactories, () -> disallowSlowQueries); pluginsService.onIndexModule(indexModule); return indexModule.newIndexMapperService(xContentRegistry, mapperRegistry, scriptService); } @@ -1572,6 +1579,10 @@ protected void doRun() { } } + private void setDisallowSlowQueries(Boolean disallowSlowQueries) { + this.disallowSlowQueries = disallowSlowQueries; + } + // visible for testing public boolean allPendingDanglingIndicesWritten() { return nodeWriteDanglingIndicesInfo == false || diff --git a/server/src/main/java/org/elasticsearch/search/SearchService.java b/server/src/main/java/org/elasticsearch/search/SearchService.java index 84c4903dfd838..c223feae90531 100644 --- a/server/src/main/java/org/elasticsearch/search/SearchService.java +++ b/server/src/main/java/org/elasticsearch/search/SearchService.java @@ -134,6 +134,8 @@ public class SearchService extends AbstractLifecycleComponent implements IndexEv Setting.positiveTimeSetting("search.max_keep_alive", timeValueHours(24), Property.NodeScope, Property.Dynamic); public static final Setting KEEPALIVE_INTERVAL_SETTING = Setting.positiveTimeSetting("search.keep_alive_interval", timeValueMinutes(1), Property.NodeScope); + public static final Setting DISALLOW_SLOW_QUERIES = + Setting.boolSetting("search.disallow_slow_queries", false, Property.NodeScope, Property.Dynamic); /** * Enables low-level, frequent search cancellation checks. Enabling low-level checks will make long running searches to react diff --git a/server/src/test/java/org/elasticsearch/index/mapper/IgnoredFieldTypeTests.java b/server/src/test/java/org/elasticsearch/index/mapper/IgnoredFieldTypeTests.java index e0cd3b1d153fd..fe18f75b0dc18 100644 --- a/server/src/test/java/org/elasticsearch/index/mapper/IgnoredFieldTypeTests.java +++ b/server/src/test/java/org/elasticsearch/index/mapper/IgnoredFieldTypeTests.java @@ -26,6 +26,7 @@ import org.apache.lucene.search.RegexpQuery; import org.apache.lucene.search.WildcardQuery; import org.apache.lucene.util.BytesRef; +import org.elasticsearch.ElasticsearchException; public class IgnoredFieldTypeTests extends FieldTypeTestCase { @@ -40,7 +41,12 @@ public void testPrefixQuery() { ft.setIndexOptions(IndexOptions.DOCS); Query expected = new PrefixQuery(new Term("field", new BytesRef("foo*"))); - assertEquals(expected, ft.prefixQuery("foo*", null, null)); + assertEquals(expected, ft.prefixQuery("foo*", null, MOCK_QSC)); + + ElasticsearchException ee = expectThrows(ElasticsearchException.class, + () -> ft.prefixQuery("foo*", null, MOCK_QSC_DISALLOW_SLOW)); + assertEquals("prefix queries cannot be executed when 'search.disallow_slow_queries' is set to true", + ee.getMessage()); } public void testRegexpQuery() { @@ -49,7 +55,12 @@ public void testRegexpQuery() { ft.setIndexOptions(IndexOptions.DOCS); Query expected = new RegexpQuery(new Term("field", new BytesRef("foo?"))); - assertEquals(expected, ft.regexpQuery("foo?", 0, 10, null, null)); + assertEquals(expected, ft.regexpQuery("foo?", 0, 10, null, MOCK_QSC)); + + ElasticsearchException ee = expectThrows(ElasticsearchException.class, + () -> ft.regexpQuery("foo?", randomInt(10), randomInt(10) + 1, null, MOCK_QSC_DISALLOW_SLOW)); + assertEquals("regexp queries cannot be executed when 'search.disallow_slow_queries' is set to true", + ee.getMessage()); } public void testWildcardQuery() { @@ -58,6 +69,11 @@ public void testWildcardQuery() { ft.setIndexOptions(IndexOptions.DOCS); Query expected = new WildcardQuery(new Term("field", new BytesRef("foo*"))); - assertEquals(expected, ft.wildcardQuery("foo*", null, null)); + assertEquals(expected, ft.wildcardQuery("foo*", null, MOCK_QSC)); + + ElasticsearchException ee = expectThrows(ElasticsearchException.class, + () -> ft.wildcardQuery("valu*", null, MOCK_QSC_DISALLOW_SLOW)); + assertEquals("wildcard queries cannot be executed when 'search.disallow_slow_queries' is set to true", + ee.getMessage()); } } diff --git a/server/src/test/java/org/elasticsearch/index/mapper/KeywordFieldTypeTests.java b/server/src/test/java/org/elasticsearch/index/mapper/KeywordFieldTypeTests.java index eae5b4ac7d2ab..5dc517be175aa 100644 --- a/server/src/test/java/org/elasticsearch/index/mapper/KeywordFieldTypeTests.java +++ b/server/src/test/java/org/elasticsearch/index/mapper/KeywordFieldTypeTests.java @@ -34,6 +34,7 @@ import org.apache.lucene.search.TermInSetQuery; import org.apache.lucene.search.TermQuery; import org.apache.lucene.util.BytesRef; +import org.elasticsearch.ElasticsearchException; import org.elasticsearch.common.lucene.Lucene; import org.elasticsearch.common.unit.Fuzziness; import org.elasticsearch.index.analysis.AnalyzerScope; @@ -155,12 +156,17 @@ public void testRegexpQuery() { ft.setName("field"); ft.setIndexOptions(IndexOptions.DOCS); assertEquals(new RegexpQuery(new Term("field","foo.*")), - ft.regexpQuery("foo.*", 0, 10, null, null)); + ft.regexpQuery("foo.*", 0, 10, null, MOCK_QSC)); ft.setIndexOptions(IndexOptions.NONE); IllegalArgumentException e = expectThrows(IllegalArgumentException.class, - () -> ft.regexpQuery("foo.*", 0, 10, null, null)); + () -> ft.regexpQuery("foo.*", 0, 10, null, MOCK_QSC)); assertEquals("Cannot search on field [field] since it is not indexed.", e.getMessage()); + + ElasticsearchException ee = expectThrows(ElasticsearchException.class, + () -> ft.regexpQuery("foo.*", randomInt(10), randomInt(10) + 1, null, MOCK_QSC_DISALLOW_SLOW)); + assertEquals("regexp queries cannot be executed when 'search.disallow_slow_queries' is set to true", + ee.getMessage()); } public void testFuzzyQuery() { @@ -168,12 +174,18 @@ public void testFuzzyQuery() { ft.setName("field"); ft.setIndexOptions(IndexOptions.DOCS); assertEquals(new FuzzyQuery(new Term("field","foo"), 2, 1, 50, true), - ft.fuzzyQuery("foo", Fuzziness.fromEdits(2), 1, 50, true)); + ft.fuzzyQuery("foo", Fuzziness.fromEdits(2), 1, 50, true, MOCK_QSC)); ft.setIndexOptions(IndexOptions.NONE); IllegalArgumentException e = expectThrows(IllegalArgumentException.class, - () -> ft.fuzzyQuery("foo", Fuzziness.fromEdits(2), 1, 50, true)); + () -> ft.fuzzyQuery("foo", Fuzziness.fromEdits(2), 1, 50, true, MOCK_QSC)); assertEquals("Cannot search on field [field] since it is not indexed.", e.getMessage()); + + ElasticsearchException ee = expectThrows(ElasticsearchException.class, + () -> ft.fuzzyQuery("foo", Fuzziness.AUTO, randomInt(10) + 1, randomInt(10) + 1, + randomBoolean(), MOCK_QSC_DISALLOW_SLOW)); + assertEquals("fuzzy queries cannot be executed when 'search.disallow_slow_queries' is set to true", + ee.getMessage()); } public void testNormalizeQueries() { diff --git a/server/src/test/java/org/elasticsearch/index/mapper/RoutingFieldTypeTests.java b/server/src/test/java/org/elasticsearch/index/mapper/RoutingFieldTypeTests.java index 6f68d28c0176a..e0144abf6bf91 100644 --- a/server/src/test/java/org/elasticsearch/index/mapper/RoutingFieldTypeTests.java +++ b/server/src/test/java/org/elasticsearch/index/mapper/RoutingFieldTypeTests.java @@ -25,8 +25,10 @@ import org.apache.lucene.search.RegexpQuery; import org.apache.lucene.search.WildcardQuery; import org.apache.lucene.util.BytesRef; +import org.elasticsearch.ElasticsearchException; public class RoutingFieldTypeTests extends FieldTypeTestCase { + @Override protected MappedFieldType createDefaultFieldType() { return new RoutingFieldMapper.RoutingFieldType(); @@ -38,7 +40,12 @@ public void testPrefixQuery() { ft.setIndexOptions(IndexOptions.DOCS); Query expected = new PrefixQuery(new Term("field", new BytesRef("foo*"))); - assertEquals(expected, ft.prefixQuery("foo*", null, null)); + assertEquals(expected, ft.prefixQuery("foo*", null, MOCK_QSC)); + + ElasticsearchException ee = expectThrows(ElasticsearchException.class, + () -> ft.prefixQuery("foo*", null, MOCK_QSC_DISALLOW_SLOW)); + assertEquals("prefix queries cannot be executed when 'search.disallow_slow_queries' is set to true", + ee.getMessage()); } public void testRegexpQuery() { @@ -47,7 +54,12 @@ public void testRegexpQuery() { ft.setIndexOptions(IndexOptions.DOCS); Query expected = new RegexpQuery(new Term("field", new BytesRef("foo?"))); - assertEquals(expected, ft.regexpQuery("foo?", 0, 10, null, null)); + assertEquals(expected, ft.regexpQuery("foo?", 0, 10, null, MOCK_QSC)); + + ElasticsearchException ee = expectThrows(ElasticsearchException.class, + () -> ft.regexpQuery("foo?", randomInt(10), randomInt(10) + 1, null, MOCK_QSC_DISALLOW_SLOW)); + assertEquals("regexp queries cannot be executed when 'search.disallow_slow_queries' is set to true", + ee.getMessage()); } public void testWildcardQuery() { @@ -56,6 +68,11 @@ public void testWildcardQuery() { ft.setIndexOptions(IndexOptions.DOCS); Query expected = new WildcardQuery(new Term("field", new BytesRef("foo*"))); - assertEquals(expected, ft.wildcardQuery("foo*", null, null)); + assertEquals(expected, ft.wildcardQuery("foo*", null, MOCK_QSC)); + + ElasticsearchException ee = expectThrows(ElasticsearchException.class, + () -> ft.wildcardQuery("valu*", null, MOCK_QSC_DISALLOW_SLOW)); + assertEquals("wildcard queries cannot be executed when 'search.disallow_slow_queries' is set to true", + ee.getMessage()); } } diff --git a/server/src/test/java/org/elasticsearch/index/mapper/TextFieldTypeTests.java b/server/src/test/java/org/elasticsearch/index/mapper/TextFieldTypeTests.java index da58907355202..670ebcac60be1 100644 --- a/server/src/test/java/org/elasticsearch/index/mapper/TextFieldTypeTests.java +++ b/server/src/test/java/org/elasticsearch/index/mapper/TextFieldTypeTests.java @@ -34,6 +34,7 @@ import org.apache.lucene.util.automaton.Automata; import org.apache.lucene.util.automaton.Automaton; import org.apache.lucene.util.automaton.Operations; +import org.elasticsearch.ElasticsearchException; import org.elasticsearch.common.unit.Fuzziness; import org.junit.Before; @@ -45,6 +46,7 @@ import static org.hamcrest.Matchers.equalTo; public class TextFieldTypeTests extends FieldTypeTestCase { + @Override protected MappedFieldType createDefaultFieldType() { return new TextFieldMapper.TextFieldType(); @@ -135,12 +137,17 @@ public void testRegexpQuery() { ft.setName("field"); ft.setIndexOptions(IndexOptions.DOCS); assertEquals(new RegexpQuery(new Term("field","foo.*")), - ft.regexpQuery("foo.*", 0, 10, null, null)); + ft.regexpQuery("foo.*", 0, 10, null, MOCK_QSC)); ft.setIndexOptions(IndexOptions.NONE); IllegalArgumentException e = expectThrows(IllegalArgumentException.class, - () -> ft.regexpQuery("foo.*", 0, 10, null, null)); + () -> ft.regexpQuery("foo.*", 0, 10, null, MOCK_QSC)); assertEquals("Cannot search on field [field] since it is not indexed.", e.getMessage()); + + ElasticsearchException ee = expectThrows(ElasticsearchException.class, + () -> ft.regexpQuery("foo.*", randomInt(10), randomInt(10) + 1, null, MOCK_QSC_DISALLOW_SLOW)); + assertEquals("regexp queries cannot be executed when 'search.disallow_slow_queries' is set to true", + ee.getMessage()); } public void testFuzzyQuery() { @@ -148,12 +155,18 @@ public void testFuzzyQuery() { ft.setName("field"); ft.setIndexOptions(IndexOptions.DOCS); assertEquals(new FuzzyQuery(new Term("field","foo"), 2, 1, 50, true), - ft.fuzzyQuery("foo", Fuzziness.fromEdits(2), 1, 50, true)); + ft.fuzzyQuery("foo", Fuzziness.fromEdits(2), 1, 50, true, MOCK_QSC)); ft.setIndexOptions(IndexOptions.NONE); IllegalArgumentException e = expectThrows(IllegalArgumentException.class, - () -> ft.fuzzyQuery("foo", Fuzziness.fromEdits(2), 1, 50, true)); + () -> ft.fuzzyQuery("foo", Fuzziness.fromEdits(2), 1, 50, true, MOCK_QSC)); assertEquals("Cannot search on field [field] since it is not indexed.", e.getMessage()); + + ElasticsearchException ee = expectThrows(ElasticsearchException.class, + () -> ft.fuzzyQuery("foo", Fuzziness.AUTO, randomInt(10) + 1, randomInt(10) + 1, + randomBoolean(), MOCK_QSC_DISALLOW_SLOW)); + assertEquals("fuzzy queries cannot be executed when 'search.disallow_slow_queries' is set to true", + ee.getMessage()); } public void testIndexPrefixes() { @@ -161,13 +174,18 @@ public void testIndexPrefixes() { ft.setName("field"); ft.setPrefixFieldType(new TextFieldMapper.PrefixFieldType("field", "field._index_prefix", 2, 10)); - Query q = ft.prefixQuery("goin", CONSTANT_SCORE_REWRITE, null); + Query q = ft.prefixQuery("goin", CONSTANT_SCORE_REWRITE, randomMockShardContext()); assertEquals(new ConstantScoreQuery(new TermQuery(new Term("field._index_prefix", "goin"))), q); - q = ft.prefixQuery("internationalisatio", CONSTANT_SCORE_REWRITE, null); + q = ft.prefixQuery("internationalisatio", CONSTANT_SCORE_REWRITE, MOCK_QSC); assertEquals(new PrefixQuery(new Term("field", "internationalisatio")), q); - q = ft.prefixQuery("g", CONSTANT_SCORE_REWRITE, null); + ElasticsearchException ee = expectThrows(ElasticsearchException.class, + () -> ft.prefixQuery("internationalisatio", null, MOCK_QSC_DISALLOW_SLOW)); + assertEquals("prefix queries cannot be executed when 'search.disallow_slow_queries' is set to true", + ee.getMessage()); + + q = ft.prefixQuery("g", CONSTANT_SCORE_REWRITE, randomMockShardContext()); Automaton automaton = Operations.concatenate(Arrays.asList(Automata.makeChar('g'), Automata.makeAnyChar())); diff --git a/test/framework/src/main/java/org/elasticsearch/index/mapper/FieldTypeTestCase.java b/test/framework/src/main/java/org/elasticsearch/index/mapper/FieldTypeTestCase.java index 8914bad5c4102..baa13c01baa9d 100644 --- a/test/framework/src/main/java/org/elasticsearch/index/mapper/FieldTypeTestCase.java +++ b/test/framework/src/main/java/org/elasticsearch/index/mapper/FieldTypeTestCase.java @@ -31,9 +31,15 @@ import java.util.Arrays; import java.util.List; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + /** Base test case for subclasses of MappedFieldType */ public abstract class FieldTypeTestCase extends ESTestCase { + public static final QueryShardContext MOCK_QSC = createMockQueryShardContext(false); + public static final QueryShardContext MOCK_QSC_DISALLOW_SLOW = createMockQueryShardContext(true); + /** Abstraction for mutating a property of a MappedFieldType */ public abstract static class Modifier { /** The name of the property that is being modified. Used in test failure messages. */ @@ -243,6 +249,15 @@ protected String toString(MappedFieldType ft) { "} " + super.toString(); } + protected QueryShardContext randomMockShardContext() { + return randomFrom(MOCK_QSC, MOCK_QSC_DISALLOW_SLOW); + } + static QueryShardContext createMockQueryShardContext(boolean disallowSlowQueries) { + QueryShardContext queryShardContext = mock(QueryShardContext.class); + when(queryShardContext.isDisallowSlowQueries()).thenReturn(disallowSlowQueries); + return queryShardContext; + } + public void testClone() { MappedFieldType fieldType = createNamedDefaultFieldType(); MappedFieldType clone = fieldType.clone(); diff --git a/x-pack/plugin/mapper-flattened/src/main/java/org/elasticsearch/xpack/flattened/mapper/FlatObjectFieldMapper.java b/x-pack/plugin/mapper-flattened/src/main/java/org/elasticsearch/xpack/flattened/mapper/FlatObjectFieldMapper.java index 5928d9889c8c8..5c182284e0c20 100644 --- a/x-pack/plugin/mapper-flattened/src/main/java/org/elasticsearch/xpack/flattened/mapper/FlatObjectFieldMapper.java +++ b/x-pack/plugin/mapper-flattened/src/main/java/org/elasticsearch/xpack/flattened/mapper/FlatObjectFieldMapper.java @@ -306,7 +306,7 @@ public Query rangeQuery(Object lowerTerm, @Override public Query fuzzyQuery(Object value, Fuzziness fuzziness, int prefixLength, int maxExpansions, - boolean transpositions) { + boolean transpositions, QueryShardContext context) { throw new UnsupportedOperationException("[fuzzy] queries are not currently supported on keyed " + "[" + CONTENT_TYPE + "] fields."); } diff --git a/x-pack/plugin/mapper-flattened/src/test/java/org/elasticsearch/xpack/flattened/mapper/KeyedFlatObjectFieldTypeTests.java b/x-pack/plugin/mapper-flattened/src/test/java/org/elasticsearch/xpack/flattened/mapper/KeyedFlatObjectFieldTypeTests.java index 46901035c8a96..fce6de477704e 100644 --- a/x-pack/plugin/mapper-flattened/src/test/java/org/elasticsearch/xpack/flattened/mapper/KeyedFlatObjectFieldTypeTests.java +++ b/x-pack/plugin/mapper-flattened/src/test/java/org/elasticsearch/xpack/flattened/mapper/KeyedFlatObjectFieldTypeTests.java @@ -15,6 +15,7 @@ import org.apache.lucene.search.TermQuery; import org.apache.lucene.search.TermRangeQuery; import org.apache.lucene.util.BytesRef; +import org.elasticsearch.ElasticsearchException; import org.elasticsearch.common.unit.Fuzziness; import org.elasticsearch.index.mapper.FieldTypeTestCase; import org.elasticsearch.index.mapper.MappedFieldType; @@ -98,7 +99,12 @@ public void testPrefixQuery() { ft.setName("field"); Query expected = new PrefixQuery(new Term("field", "key\0val")); - assertEquals(expected, ft.prefixQuery("val", MultiTermQuery.CONSTANT_SCORE_REWRITE, null)); + assertEquals(expected, ft.prefixQuery("val", MultiTermQuery.CONSTANT_SCORE_REWRITE, MOCK_QSC)); + + ElasticsearchException ee = expectThrows(ElasticsearchException.class, + () -> ft.prefixQuery("val", MultiTermQuery.CONSTANT_SCORE_REWRITE, MOCK_QSC_DISALLOW_SLOW)); + assertEquals("prefix queries cannot be executed when 'search.disallow_slow_queries' is set to true", + ee.getMessage()); } public void testFuzzyQuery() { @@ -106,7 +112,7 @@ public void testFuzzyQuery() { ft.setName("field"); UnsupportedOperationException e = expectThrows(UnsupportedOperationException.class, - () -> ft.fuzzyQuery("valuee", Fuzziness.fromEdits(2), 1, 50, true)); + () -> ft.fuzzyQuery("value", Fuzziness.fromEdits(2), 1, 50, true, randomMockShardContext())); assertEquals("[fuzzy] queries are not currently supported on keyed [flattened] fields.", e.getMessage()); } @@ -140,7 +146,7 @@ public void testRegexpQuery() { ft.setName("field"); UnsupportedOperationException e = expectThrows(UnsupportedOperationException.class, - () -> ft.regexpQuery("valu*", 0, 10, null, null)); + () -> ft.regexpQuery("valu*", 0, 10, null, randomMockShardContext())); assertEquals("[regexp] queries are not currently supported on keyed [flattened] fields.", e.getMessage()); } @@ -149,7 +155,7 @@ public void testWildcardQuery() { ft.setName("field"); UnsupportedOperationException e = expectThrows(UnsupportedOperationException.class, - () -> ft.wildcardQuery("valu*", null, null)); + () -> ft.wildcardQuery("valu*", null, randomMockShardContext())); assertEquals("[wildcard] queries are not currently supported on keyed [flattened] fields.", e.getMessage()); } } diff --git a/x-pack/plugin/mapper-flattened/src/test/java/org/elasticsearch/xpack/flattened/mapper/RootFlatObjectFieldTypeTests.java b/x-pack/plugin/mapper-flattened/src/test/java/org/elasticsearch/xpack/flattened/mapper/RootFlatObjectFieldTypeTests.java index be297663c6e74..2b856ebd01dc5 100644 --- a/x-pack/plugin/mapper-flattened/src/test/java/org/elasticsearch/xpack/flattened/mapper/RootFlatObjectFieldTypeTests.java +++ b/x-pack/plugin/mapper-flattened/src/test/java/org/elasticsearch/xpack/flattened/mapper/RootFlatObjectFieldTypeTests.java @@ -16,6 +16,7 @@ import org.apache.lucene.search.TermRangeQuery; import org.apache.lucene.search.WildcardQuery; import org.apache.lucene.util.BytesRef; +import org.elasticsearch.ElasticsearchException; import org.elasticsearch.common.unit.Fuzziness; import org.elasticsearch.index.mapper.FieldNamesFieldMapper; import org.elasticsearch.index.mapper.FieldTypeTestCase; @@ -78,8 +79,14 @@ public void testFuzzyQuery() { ft.setName("field"); Query expected = new FuzzyQuery(new Term("field", "value"), 2, 1, 50, true); - Query actual = ft.fuzzyQuery("value", Fuzziness.fromEdits(2), 1, 50, true); + Query actual = ft.fuzzyQuery("value", Fuzziness.fromEdits(2), 1, 50, true, MOCK_QSC); assertEquals(expected, actual); + + ElasticsearchException ee = expectThrows(ElasticsearchException.class, + () -> ft.fuzzyQuery("value", Fuzziness.AUTO, randomInt(10) + 1, randomInt(10) + 1, + randomBoolean(), MOCK_QSC_DISALLOW_SLOW)); + assertEquals("fuzzy queries cannot be executed when 'search.disallow_slow_queries' is set to true", + ee.getMessage()); } public void testRangeQuery() { @@ -102,8 +109,13 @@ public void testRegexpQuery() { ft.setName("field"); Query expected = new RegexpQuery(new Term("field", "val.*")); - Query actual = ft.regexpQuery("val.*", 0, 10, null, null); + Query actual = ft.regexpQuery("val.*", 0, 10, null, MOCK_QSC); assertEquals(expected, actual); + + ElasticsearchException ee = expectThrows(ElasticsearchException.class, + () -> ft.regexpQuery("val.*", randomInt(10), randomInt(10) + 1, null, MOCK_QSC_DISALLOW_SLOW)); + assertEquals("regexp queries cannot be executed when 'search.disallow_slow_queries' is set to true", + ee.getMessage()); } public void testWildcardQuery() { @@ -111,6 +123,11 @@ public void testWildcardQuery() { ft.setName("field"); Query expected = new WildcardQuery(new Term("field", new BytesRef("valu*"))); - assertEquals(expected, ft.wildcardQuery("valu*", null, null)); + assertEquals(expected, ft.wildcardQuery("valu*", null, MOCK_QSC)); + + ElasticsearchException ee = expectThrows(ElasticsearchException.class, + () -> ft.wildcardQuery("valu*", null, MOCK_QSC_DISALLOW_SLOW)); + assertEquals("wildcard queries cannot be executed when 'search.disallow_slow_queries' is set to true", + ee.getMessage()); } } diff --git a/x-pack/plugin/watcher/src/test/java/org/elasticsearch/xpack/watcher/WatcherPluginTests.java b/x-pack/plugin/watcher/src/test/java/org/elasticsearch/xpack/watcher/WatcherPluginTests.java index 5575915188351..8141bb6ad45b9 100644 --- a/x-pack/plugin/watcher/src/test/java/org/elasticsearch/xpack/watcher/WatcherPluginTests.java +++ b/x-pack/plugin/watcher/src/test/java/org/elasticsearch/xpack/watcher/WatcherPluginTests.java @@ -74,7 +74,8 @@ public void testWatcherDisabledTests() throws Exception { IndexSettings indexSettings = IndexSettingsModule.newIndexSettings(Watch.INDEX, settings); AnalysisRegistry registry = new AnalysisRegistry(TestEnvironment.newEnvironment(settings), emptyMap(), emptyMap(), emptyMap(), emptyMap(), emptyMap(), emptyMap(), emptyMap(), emptyMap(), emptyMap()); - IndexModule indexModule = new IndexModule(indexSettings, registry, new InternalEngineFactory(), Collections.emptyMap()); + IndexModule indexModule = new IndexModule(indexSettings, registry, new InternalEngineFactory(), Collections.emptyMap(), + () -> false); // this will trip an assertion if the watcher indexing operation listener is null (which it is) but we try to add it watcher.onIndexModule(indexModule);