diff --git a/rest-api-spec/build.gradle b/rest-api-spec/build.gradle index 85d4adddae9d2..2e80aa2b16218 100644 --- a/rest-api-spec/build.gradle +++ b/rest-api-spec/build.gradle @@ -80,7 +80,10 @@ def v7compatibilityNotSupportedTests = { 'field_caps/30_filter/Field caps with index filter', //behaviour change after #63692 4digits dates are parsed as epoch and in quotes as year - 'indices.forcemerge/10_basic/Check deprecation warning when incompatible only_expunge_deletes and max_num_segments values are both set', //#44761 bug fix + 'indices.forcemerge/10_basic/Check deprecation warning when incompatible only_expunge_deletes and max_num_segments values are both set', //#44761 bug fix, + + 'search/340_type_query/type query' //#47207 type query throws exception in compatible mode + ] } tasks.named("yamlRestCompatTest").configure { @@ -91,7 +94,6 @@ tasks.named("yamlRestCompatTest").configure { systemProperty 'tests.rest.blacklist', ([ 'search.aggregation/200_top_hits_metric/top_hits aggregation with sequence numbers', 'search/310_match_bool_prefix/multi_match multiple fields with cutoff_frequency throws exception', //cutoff_frequency - 'search/340_type_query/type query', // type_query - probably should behave like match_all ] + v7compatibilityNotSupportedTests()) .join(',') diff --git a/rest-api-spec/src/yamlRestCompatTest/resources/rest-api-spec/test/v7compat/search/10_type_query.yml b/rest-api-spec/src/yamlRestCompatTest/resources/rest-api-spec/test/v7compat/search/10_type_query.yml new file mode 100644 index 0000000000000..fa4e20fdfa6fe --- /dev/null +++ b/rest-api-spec/src/yamlRestCompatTest/resources/rest-api-spec/test/v7compat/search/10_type_query.yml @@ -0,0 +1,52 @@ +--- +setup: + - skip: + features: + - "headers" + - "allowed_warnings_regex" +--- +type query throws exception when used: + - do: + index: + index: "test1" + id: 1 + type: "cat" + refresh: true + body: + foo: "bar" + headers: + Content-Type: "application/vnd.elasticsearch+json;compatible-with=7" + Accept: "application/vnd.elasticsearch+json;compatible-with=7" + allowed_warnings_regex: + - "\\[types removal\\].*" + + - do: + catch: /\[types removal\] Type queries are deprecated, prefer to filter on a field instead./ + search: + rest_total_hits_as_int: true + index: "test1" + body: + query: + type: + value: "cat" + headers: + Content-Type: "application/vnd.elasticsearch+json;compatible-with=7" + Accept: "application/vnd.elasticsearch+json;compatible-with=7" + allowed_warnings_regex: + - "\\[types removal\\].*" + + - do: + catch: /\[types removal\] Type queries are deprecated, prefer to filter on a field instead./ + search: + rest_total_hits_as_int: true + index: "test1" + body: + query: + type: + value: "_doc" + headers: + Content-Type: "application/vnd.elasticsearch+json;compatible-with=7" + Accept: "application/vnd.elasticsearch+json;compatible-with=7" + allowed_warnings_regex: + - "\\[types removal\\].*" + diff --git a/server/src/main/java/org/elasticsearch/index/query/TypeQueryV7Builder.java b/server/src/main/java/org/elasticsearch/index/query/TypeQueryV7Builder.java new file mode 100644 index 0000000000000..915662f4e9893 --- /dev/null +++ b/server/src/main/java/org/elasticsearch/index/query/TypeQueryV7Builder.java @@ -0,0 +1,97 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +package org.elasticsearch.index.query; + +import org.apache.lucene.search.MatchNoDocsQuery; +import org.apache.lucene.search.Query; +import org.elasticsearch.common.ParsingException; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.common.logging.DeprecationLogger; +import org.elasticsearch.common.xcontent.ObjectParser; +import org.elasticsearch.common.xcontent.ParseField; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.core.RestApiVersion; +import org.elasticsearch.index.mapper.MapperService; + +import java.io.IOException; + +public class TypeQueryV7Builder extends AbstractQueryBuilder { + private static final DeprecationLogger deprecationLogger = DeprecationLogger.getLogger(TypeQueryV7Builder.class); + public static final String TYPES_DEPRECATION_MESSAGE = "[types removal] Type queries are deprecated, " + + "prefer to filter on a field instead."; + + private static final String NAME = "type"; + public static final ParseField NAME_V7 = new ParseField(NAME).forRestApiVersion(RestApiVersion.equalTo(RestApiVersion.V_7)); + private static final ParseField VALUE_FIELD = new ParseField("value"); + private static final ObjectParser PARSER = new ObjectParser<>(NAME, TypeQueryV7Builder::new); + + static { + PARSER.declareString(QueryBuilder::queryName, + AbstractQueryBuilder.NAME_FIELD.forRestApiVersion(RestApiVersion.equalTo(RestApiVersion.V_7))); + PARSER.declareFloat(QueryBuilder::boost, + AbstractQueryBuilder.BOOST_FIELD.forRestApiVersion(RestApiVersion.equalTo(RestApiVersion.V_7))); + PARSER.declareString(TypeQueryV7Builder::setValue, + VALUE_FIELD.forRestApiVersion(RestApiVersion.equalTo(RestApiVersion.V_7))); + } + + private String value; + + public TypeQueryV7Builder() { + } + + /** + * Read from a stream. + */ + public TypeQueryV7Builder(StreamInput in) throws IOException { + super(in); + } + + @Override + protected void doWriteTo(StreamOutput out) throws IOException { + } + + @Override + protected void doXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(NAME); + builder.field(VALUE_FIELD.getPreferredName(), MapperService.SINGLE_MAPPING_NAME); + printBoostAndQueryName(builder); + builder.endObject(); + } + + @Override + protected Query doToQuery(SearchExecutionContext context) throws IOException { + return new MatchNoDocsQuery(); + } + + @Override + protected boolean doEquals(TypeQueryV7Builder other) { + return true; + } + + @Override + protected int doHashCode() { + return 0; + } + + public static TypeQueryV7Builder fromXContent(XContentParser parser) throws IOException { + deprecationLogger.compatibleApiWarning("type_query", TYPES_DEPRECATION_MESSAGE); + throw new ParsingException(parser.getTokenLocation(), TYPES_DEPRECATION_MESSAGE); + } + + @Override + public String getWriteableName() { + return NAME; + } + + public void setValue(String value){ + this.value = value; + } +} diff --git a/server/src/main/java/org/elasticsearch/search/SearchModule.java b/server/src/main/java/org/elasticsearch/search/SearchModule.java index 40e25d0cd670d..b1ecd5f61460d 100644 --- a/server/src/main/java/org/elasticsearch/search/SearchModule.java +++ b/server/src/main/java/org/elasticsearch/search/SearchModule.java @@ -11,7 +11,6 @@ import org.apache.lucene.search.BooleanQuery; import org.elasticsearch.common.CheckedBiConsumer; import org.elasticsearch.common.NamedRegistry; -import org.elasticsearch.common.xcontent.ParseField; import org.elasticsearch.common.geo.GeoShapeType; import org.elasticsearch.common.geo.ShapesAvailability; import org.elasticsearch.common.io.stream.NamedWriteableRegistry; @@ -21,8 +20,10 @@ import org.elasticsearch.common.settings.Setting; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.xcontent.NamedXContentRegistry; +import org.elasticsearch.common.xcontent.ParseField; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.core.Nullable; +import org.elasticsearch.core.RestApiVersion; import org.elasticsearch.index.query.BoolQueryBuilder; import org.elasticsearch.index.query.BoostingQueryBuilder; import org.elasticsearch.index.query.CombinedFieldsQueryBuilder; @@ -67,6 +68,7 @@ import org.elasticsearch.index.query.TermQueryBuilder; import org.elasticsearch.index.query.TermsQueryBuilder; import org.elasticsearch.index.query.TermsSetQueryBuilder; +import org.elasticsearch.index.query.TypeQueryV7Builder; import org.elasticsearch.index.query.WildcardQueryBuilder; import org.elasticsearch.index.query.WrapperQueryBuilder; import org.elasticsearch.index.query.functionscore.ExponentialDecayFunctionBuilder; @@ -839,6 +841,10 @@ private void registerQueryParsers(List plugins) { } registerFromPlugin(plugins, SearchPlugin::getQueries, this::registerQuery); + + if (RestApiVersion.minimumSupported() == RestApiVersion.V_7) { + registerQuery(new QuerySpec<>(TypeQueryV7Builder.NAME_V7, TypeQueryV7Builder::new, TypeQueryV7Builder::fromXContent)); + } } private void registerIntervalsSourceProviders() { @@ -893,4 +899,5 @@ private void registerBoolQuery(ParseField name, Writeable.Reader r public FetchPhase getFetchPhase() { return new FetchPhase(fetchSubPhases); } + } diff --git a/server/src/test/java/org/elasticsearch/search/SearchModuleTests.java b/server/src/test/java/org/elasticsearch/search/SearchModuleTests.java index 9241b803acabe..e74dd18e767b3 100644 --- a/server/src/test/java/org/elasticsearch/search/SearchModuleTests.java +++ b/server/src/test/java/org/elasticsearch/search/SearchModuleTests.java @@ -25,6 +25,7 @@ import org.elasticsearch.index.query.QueryRewriteContext; import org.elasticsearch.index.query.SearchExecutionContext; import org.elasticsearch.index.query.TermQueryBuilder; +import org.elasticsearch.index.query.TypeQueryV7Builder; import org.elasticsearch.index.query.functionscore.GaussDecayFunctionBuilder; import org.elasticsearch.plugins.SearchPlugin; import org.elasticsearch.search.aggregations.AggregationBuilder; @@ -241,11 +242,14 @@ public void testRegisteredQueries() { List allSupportedQueries = new ArrayList<>(); Collections.addAll(allSupportedQueries, NON_DEPRECATED_QUERIES); Collections.addAll(allSupportedQueries, DEPRECATED_QUERIES); + Collections.addAll(allSupportedQueries, REST_COMPATIBLE_QUERIES); + SearchModule module = new SearchModule(Settings.EMPTY, emptyList()); Set registeredNonDeprecated = module.getNamedXContents().stream() .filter(e -> e.categoryClass.equals(QueryBuilder.class)) .filter(e -> e.name.getAllReplacedWith() == null) + .filter(e -> RestApiVersion.current().matches(e.restApiCompatibility)) .map(e -> e.name.getPreferredName()) .collect(toSet()); Set registeredAll = module.getNamedXContents().stream() @@ -389,6 +393,7 @@ public CheckedBiConsumer getReque //add here deprecated queries to make sure we log a deprecation warnings when they are used private static final String[] DEPRECATED_QUERIES = new String[] {"field_masking_span", "geo_polygon"}; + private static final String[] REST_COMPATIBLE_QUERIES = new String[] {TypeQueryV7Builder.NAME_V7.getPreferredName()}; /** * Dummy test {@link AggregationBuilder} used to test registering aggregation builders. @@ -669,14 +674,15 @@ public List> getQueries() { .filter(e -> RestApiVersion.minimumSupported().matches(e.restApiCompatibility)) .filter(e -> RestApiVersion.current().matches(e.restApiCompatibility)) .collect(toSet()), - hasSize(searchModule.getNamedXContents().size() - 1)); + hasSize(searchModule.getNamedXContents().size()- REST_COMPATIBLE_QUERIES.length - 1 )); final List compatEntry = searchModule.getNamedXContents().stream() .filter(e -> e.categoryClass.equals(QueryBuilder.class) && - e.name.match(CompatQueryBuilder.NAME_OLD.getPreferredName(), LoggingDeprecationHandler.INSTANCE)) + RestApiVersion.minimumSupported().matches(e.name.getForRestApiVersion()) // v7 compatbile + && RestApiVersion.current().matches(e.name.getForRestApiVersion()) == false) // but not v8 compatible .collect(toList()); - assertThat(compatEntry, hasSize(1)); + assertThat(compatEntry, hasSize(REST_COMPATIBLE_QUERIES.length + 1));//+1 because of registered in the test assertTrue(RestApiVersion.minimumSupported().matches(compatEntry.get(0).restApiCompatibility)); assertFalse(RestApiVersion.current().matches(compatEntry.get(0).restApiCompatibility)); }