From 7e60cf3e544f05c89e7a32aa9dc410b95b6bcb24 Mon Sep 17 00:00:00 2001 From: Jim Ferenczi Date: Tue, 6 Jun 2017 19:35:14 +0200 Subject: [PATCH] Move parent_id query to the parent-join module (#25072) This change moves the parent_id query to the parent-join module and handles the case when only the parent-join field can be declared on an index (index with single type on). If single type is off it uses the legacy parent join field mapper and switch to the new one otherwise (default in 6). Relates #20257 --- .../index/query/QueryBuilders.java | 8 -- .../elasticsearch/search/SearchModule.java | 2 - .../index/query/MatchQueryBuilderTests.java | 2 +- .../query/MoreLikeThisQueryBuilderTests.java | 2 +- .../index/query/NestedQueryBuilderTests.java | 2 +- .../query/QueryStringQueryBuilderTests.java | 8 +- .../query/SpanTermQueryBuilderTests.java | 2 +- .../index/query/TypeQueryBuilderTests.java | 13 +- .../query/WildcardQueryBuilderTests.java | 4 +- .../search/SearchModuleTests.java | 1 - .../elasticsearch/join/ParentJoinPlugin.java | 4 +- .../join/query/JoinQueryBuilders.java | 7 + .../join}/query/ParentIdQueryBuilder.java | 52 ++++++- .../join/query/ChildQuerySearchIT.java | 32 +++-- .../join/query/HasChildQueryBuilderTests.java | 8 ++ .../query/HasParentQueryBuilderTests.java | 9 ++ .../LegacyParentIdQueryBuilderTests.java | 42 +++--- .../join/query/ParentIdQueryBuilderTests.java | 134 ++++++++++++++++++ .../rest-api-spec/test/20_parent_join.yml | 89 ++++++++++-- .../PercolateQueryBuilderTests.java | 6 +- .../test/AbstractQueryTestCase.java | 31 ++-- 21 files changed, 360 insertions(+), 98 deletions(-) rename {core/src/main/java/org/elasticsearch/index => modules/parent-join/src/main/java/org/elasticsearch/join}/query/ParentIdQueryBuilder.java (74%) rename core/src/test/java/org/elasticsearch/index/query/ParentIdQueryBuilderTests.java => modules/parent-join/src/test/java/org/elasticsearch/join/query/LegacyParentIdQueryBuilderTests.java (83%) create mode 100644 modules/parent-join/src/test/java/org/elasticsearch/join/query/ParentIdQueryBuilderTests.java diff --git a/core/src/main/java/org/elasticsearch/index/query/QueryBuilders.java b/core/src/main/java/org/elasticsearch/index/query/QueryBuilders.java index df0493d61c8f0..0aa9a43a312b3 100644 --- a/core/src/main/java/org/elasticsearch/index/query/QueryBuilders.java +++ b/core/src/main/java/org/elasticsearch/index/query/QueryBuilders.java @@ -471,14 +471,6 @@ public static MoreLikeThisQueryBuilder moreLikeThisQuery(Item[] likeItems) { return moreLikeThisQuery(null, null, likeItems); } - /** - * Constructs a new parent id query that returns all child documents of the specified type that - * point to the specified id. - */ - public static ParentIdQueryBuilder parentId(String type, String id) { - return new ParentIdQueryBuilder(type, id); - } - public static NestedQueryBuilder nestedQuery(String path, QueryBuilder query, ScoreMode scoreMode) { return new NestedQueryBuilder(path, query, scoreMode); } diff --git a/core/src/main/java/org/elasticsearch/search/SearchModule.java b/core/src/main/java/org/elasticsearch/search/SearchModule.java index fea834c02ac52..16bd9dbe8b931 100644 --- a/core/src/main/java/org/elasticsearch/search/SearchModule.java +++ b/core/src/main/java/org/elasticsearch/search/SearchModule.java @@ -52,7 +52,6 @@ import org.elasticsearch.index.query.MoreLikeThisQueryBuilder; import org.elasticsearch.index.query.MultiMatchQueryBuilder; import org.elasticsearch.index.query.NestedQueryBuilder; -import org.elasticsearch.index.query.ParentIdQueryBuilder; import org.elasticsearch.index.query.PrefixQueryBuilder; import org.elasticsearch.index.query.QueryBuilder; import org.elasticsearch.index.query.QueryParseContext; @@ -743,7 +742,6 @@ private void registerQueryParsers(List plugins) { registerQuery(new QuerySpec<>(GeoPolygonQueryBuilder.NAME, GeoPolygonQueryBuilder::new, GeoPolygonQueryBuilder::fromXContent)); registerQuery(new QuerySpec<>(ExistsQueryBuilder.NAME, ExistsQueryBuilder::new, ExistsQueryBuilder::fromXContent)); registerQuery(new QuerySpec<>(MatchNoneQueryBuilder.NAME, MatchNoneQueryBuilder::new, MatchNoneQueryBuilder::fromXContent)); - registerQuery(new QuerySpec<>(ParentIdQueryBuilder.NAME, ParentIdQueryBuilder::new, ParentIdQueryBuilder::fromXContent)); if (ShapesAvailability.JTS_AVAILABLE && ShapesAvailability.SPATIAL4J_AVAILABLE) { registerQuery(new QuerySpec<>(GeoShapeQueryBuilder.NAME, GeoShapeQueryBuilder::new, GeoShapeQueryBuilder::fromXContent)); diff --git a/core/src/test/java/org/elasticsearch/index/query/MatchQueryBuilderTests.java b/core/src/test/java/org/elasticsearch/index/query/MatchQueryBuilderTests.java index 18da1d37b7228..31687c5d9ff59 100644 --- a/core/src/test/java/org/elasticsearch/index/query/MatchQueryBuilderTests.java +++ b/core/src/test/java/org/elasticsearch/index/query/MatchQueryBuilderTests.java @@ -430,7 +430,7 @@ public void testExceptionUsingAnalyzerOnNumericField() { @Override protected void initializeAdditionalMappings(MapperService mapperService) throws IOException { - mapperService.merge("t_boost", new CompressedXContent(PutMappingRequest.buildFromSimplifiedDef("t_boost", + mapperService.merge("doc", new CompressedXContent(PutMappingRequest.buildFromSimplifiedDef("doc", "string_boost", "type=text,boost=4").string()), MapperService.MergeReason.MAPPING_UPDATE, false); } diff --git a/core/src/test/java/org/elasticsearch/index/query/MoreLikeThisQueryBuilderTests.java b/core/src/test/java/org/elasticsearch/index/query/MoreLikeThisQueryBuilderTests.java index ec34f6d87e353..4f7e461ecaa63 100644 --- a/core/src/test/java/org/elasticsearch/index/query/MoreLikeThisQueryBuilderTests.java +++ b/core/src/test/java/org/elasticsearch/index/query/MoreLikeThisQueryBuilderTests.java @@ -97,7 +97,7 @@ private static String[] randomStringFields() { private Item generateRandomItem() { String index = randomBoolean() ? getIndex().getName() : null; - String type = getRandomType(); // set to one type to avoid ambiguous types + String type = "doc"; // indexed item or artificial document Item item; if (randomBoolean()) { diff --git a/core/src/test/java/org/elasticsearch/index/query/NestedQueryBuilderTests.java b/core/src/test/java/org/elasticsearch/index/query/NestedQueryBuilderTests.java index a9e1b6ce23c9c..f25bcc879cb24 100644 --- a/core/src/test/java/org/elasticsearch/index/query/NestedQueryBuilderTests.java +++ b/core/src/test/java/org/elasticsearch/index/query/NestedQueryBuilderTests.java @@ -55,7 +55,7 @@ public class NestedQueryBuilderTests extends AbstractQueryTestCase { @Override protected TypeQueryBuilder doCreateTestQueryBuilder() { - return new TypeQueryBuilder(getRandomType()); + return new TypeQueryBuilder("doc"); } @Override @@ -40,7 +45,11 @@ protected void doAssertLuceneQuery(TypeQueryBuilder queryBuilder, Query query, S if (createShardContext().getMapperService().documentMapper(queryBuilder.type()) == null) { assertEquals(new MatchNoDocsQuery(), query); } else { - assertEquals(new TypeFieldMapper.TypesQuery(new BytesRef(queryBuilder.type())), query); + assertThat(query, + anyOf( + equalTo(new TypeFieldMapper.TypesQuery(new BytesRef(queryBuilder.type()))), + equalTo(new MatchAllDocsQuery())) + ); } } diff --git a/core/src/test/java/org/elasticsearch/index/query/WildcardQueryBuilderTests.java b/core/src/test/java/org/elasticsearch/index/query/WildcardQueryBuilderTests.java index 05264cc8ebfb6..8f24bfb4541b2 100644 --- a/core/src/test/java/org/elasticsearch/index/query/WildcardQueryBuilderTests.java +++ b/core/src/test/java/org/elasticsearch/index/query/WildcardQueryBuilderTests.java @@ -89,7 +89,7 @@ public void testIllegalArguments() { public void testEmptyValue() throws IOException { QueryShardContext context = createShardContext(); context.setAllowUnmappedFields(true); - WildcardQueryBuilder wildcardQueryBuilder = new WildcardQueryBuilder(getRandomType(), ""); + WildcardQueryBuilder wildcardQueryBuilder = new WildcardQueryBuilder("doc", ""); assertEquals(wildcardQueryBuilder.toQuery(context).getClass(), WildcardQuery.class); } @@ -129,7 +129,7 @@ public void testParseFailsWithMultipleFields() throws IOException { public void testWithMetaDataField() throws IOException { QueryShardContext context = createShardContext(); - for (String field : new String[]{"_type", "_all"}) { + for (String field : new String[]{"field1", "field2"}) { WildcardQueryBuilder wildcardQueryBuilder = new WildcardQueryBuilder(field, "toto"); Query query = wildcardQueryBuilder.toQuery(context); Query expected = new WildcardQuery(new Term(field, "toto")); diff --git a/core/src/test/java/org/elasticsearch/search/SearchModuleTests.java b/core/src/test/java/org/elasticsearch/search/SearchModuleTests.java index 96767c99b9daa..85b13974042e0 100644 --- a/core/src/test/java/org/elasticsearch/search/SearchModuleTests.java +++ b/core/src/test/java/org/elasticsearch/search/SearchModuleTests.java @@ -279,7 +279,6 @@ public List getPipelineAggregations() { "more_like_this", "multi_match", "nested", - "parent_id", "prefix", "query_string", "range", diff --git a/modules/parent-join/src/main/java/org/elasticsearch/join/ParentJoinPlugin.java b/modules/parent-join/src/main/java/org/elasticsearch/join/ParentJoinPlugin.java index 83033545cfbb7..d4ee17bc9a171 100644 --- a/modules/parent-join/src/main/java/org/elasticsearch/join/ParentJoinPlugin.java +++ b/modules/parent-join/src/main/java/org/elasticsearch/join/ParentJoinPlugin.java @@ -27,6 +27,7 @@ import org.elasticsearch.join.mapper.ParentJoinFieldMapper; import org.elasticsearch.join.query.HasChildQueryBuilder; import org.elasticsearch.join.query.HasParentQueryBuilder; +import org.elasticsearch.join.query.ParentIdQueryBuilder; import org.elasticsearch.plugins.MapperPlugin; import org.elasticsearch.plugins.Plugin; import org.elasticsearch.plugins.SearchPlugin; @@ -44,7 +45,8 @@ public ParentJoinPlugin(Settings settings) {} public List> getQueries() { return Arrays.asList( new QuerySpec<>(HasChildQueryBuilder.NAME, HasChildQueryBuilder::new, HasChildQueryBuilder::fromXContent), - new QuerySpec<>(HasParentQueryBuilder.NAME, HasParentQueryBuilder::new, HasParentQueryBuilder::fromXContent) + new QuerySpec<>(HasParentQueryBuilder.NAME, HasParentQueryBuilder::new, HasParentQueryBuilder::fromXContent), + new QuerySpec<>(ParentIdQueryBuilder.NAME, ParentIdQueryBuilder::new, ParentIdQueryBuilder::fromXContent) ); } diff --git a/modules/parent-join/src/main/java/org/elasticsearch/join/query/JoinQueryBuilders.java b/modules/parent-join/src/main/java/org/elasticsearch/join/query/JoinQueryBuilders.java index af778f400f7b3..d3ef43b7f2f1a 100644 --- a/modules/parent-join/src/main/java/org/elasticsearch/join/query/JoinQueryBuilders.java +++ b/modules/parent-join/src/main/java/org/elasticsearch/join/query/JoinQueryBuilders.java @@ -47,4 +47,11 @@ public static HasParentQueryBuilder hasParentQuery(String type, QueryBuilder que return new HasParentQueryBuilder(type, query, score); } + /** + * Constructs a new parent id query that returns all child documents of the specified type that + * point to the specified id. + */ + public static ParentIdQueryBuilder parentId(String type, String id) { + return new ParentIdQueryBuilder(type, id); + } } diff --git a/core/src/main/java/org/elasticsearch/index/query/ParentIdQueryBuilder.java b/modules/parent-join/src/main/java/org/elasticsearch/join/query/ParentIdQueryBuilder.java similarity index 74% rename from core/src/main/java/org/elasticsearch/index/query/ParentIdQueryBuilder.java rename to modules/parent-join/src/main/java/org/elasticsearch/join/query/ParentIdQueryBuilder.java index f5608df070359..b4cfa7d754e90 100644 --- a/core/src/main/java/org/elasticsearch/index/query/ParentIdQueryBuilder.java +++ b/modules/parent-join/src/main/java/org/elasticsearch/join/query/ParentIdQueryBuilder.java @@ -17,7 +17,7 @@ * under the License. */ -package org.elasticsearch.index.query; +package org.elasticsearch.join.query; import org.apache.lucene.index.Term; import org.apache.lucene.search.BooleanClause; @@ -35,6 +35,12 @@ import org.elasticsearch.index.mapper.DocumentMapper; import org.elasticsearch.index.mapper.ParentFieldMapper; import org.elasticsearch.index.mapper.TypeFieldMapper; +import org.elasticsearch.index.query.AbstractQueryBuilder; +import org.elasticsearch.index.query.QueryParseContext; +import org.elasticsearch.index.query.QueryShardContext; +import org.elasticsearch.index.query.QueryShardException; +import org.elasticsearch.join.mapper.ParentIdFieldMapper; +import org.elasticsearch.join.mapper.ParentJoinFieldMapper; import java.io.IOException; import java.util.Objects; @@ -155,6 +161,40 @@ public static ParentIdQueryBuilder fromXContent(QueryParseContext parseContext) @Override protected Query doToQuery(QueryShardContext context) throws IOException { + if (context.getIndexSettings().isSingleType() == false) { + // BWC for indices with multiple types + return doToQueryBWC(context); + } + + ParentJoinFieldMapper joinFieldMapper = ParentJoinFieldMapper.getMapper(context.getMapperService()); + if (joinFieldMapper == null) { + if (ignoreUnmapped) { + return new MatchNoDocsQuery(); + } else { + final String indexName = context.getIndexSettings().getIndex().getName(); + throw new QueryShardException(context, "[" + NAME + "] no join field found for index [" + indexName + "]"); + } + } + final ParentIdFieldMapper childMapper = joinFieldMapper.getParentIdFieldMapper(type, false); + if (childMapper == null) { + if (ignoreUnmapped) { + return new MatchNoDocsQuery(); + } else { + throw new QueryShardException(context, "[" + NAME + "] no relation found for child [" + type + "]"); + } + } + return new BooleanQuery.Builder() + .add(childMapper.fieldType().termQuery(id, context), BooleanClause.Occur.MUST) + // Need to take child type into account, otherwise a child doc of different type with the same id could match + .add(joinFieldMapper.fieldType().termQuery(type, context), BooleanClause.Occur.FILTER) + .build(); + } + + /** + * Creates parent_id query from a {@link ParentFieldMapper} + * Only used for BWC with multi-types indices + */ + private Query doToQueryBWC(QueryShardContext context) throws IOException { DocumentMapper childDocMapper = context.getMapperService().documentMapper(type); if (childDocMapper == null) { if (ignoreUnmapped) { @@ -169,11 +209,11 @@ protected Query doToQuery(QueryShardContext context) throws IOException { } String fieldName = ParentFieldMapper.joinField(parentFieldMapper.type()); - BooleanQuery.Builder query = new BooleanQuery.Builder(); - query.add(new DocValuesTermsQuery(fieldName, id), BooleanClause.Occur.MUST); - // Need to take child type into account, otherwise a child doc of different type with the same id could match - query.add(new TermQuery(new Term(TypeFieldMapper.NAME, type)), BooleanClause.Occur.FILTER); - return query.build(); + return new BooleanQuery.Builder() + .add(new DocValuesTermsQuery(fieldName, id), BooleanClause.Occur.MUST) + // Need to take child type into account, otherwise a child doc of different type with the same id could match + .add(new TermQuery(new Term(TypeFieldMapper.NAME, type)), BooleanClause.Occur.FILTER) + .build(); } @Override diff --git a/modules/parent-join/src/test/java/org/elasticsearch/join/query/ChildQuerySearchIT.java b/modules/parent-join/src/test/java/org/elasticsearch/join/query/ChildQuerySearchIT.java index a80b9a35c6344..8bd995e63cb28 100644 --- a/modules/parent-join/src/test/java/org/elasticsearch/join/query/ChildQuerySearchIT.java +++ b/modules/parent-join/src/test/java/org/elasticsearch/join/query/ChildQuerySearchIT.java @@ -77,7 +77,6 @@ import static org.elasticsearch.index.query.QueryBuilders.idsQuery; import static org.elasticsearch.index.query.QueryBuilders.matchAllQuery; import static org.elasticsearch.index.query.QueryBuilders.matchQuery; -import static org.elasticsearch.index.query.QueryBuilders.parentId; import static org.elasticsearch.index.query.QueryBuilders.prefixQuery; import static org.elasticsearch.index.query.QueryBuilders.queryStringQuery; import static org.elasticsearch.index.query.QueryBuilders.termQuery; @@ -86,6 +85,7 @@ import static org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders.weightFactorFunction; import static org.elasticsearch.join.query.JoinQueryBuilders.hasChildQuery; import static org.elasticsearch.join.query.JoinQueryBuilders.hasParentQuery; +import static org.elasticsearch.join.query.JoinQueryBuilders.parentId; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertHitCount; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertNoFailures; @@ -1262,27 +1262,31 @@ public void testParentFieldQuery() throws Exception { } public void testParentIdQuery() throws Exception { - if (legacy() == false) { - // Fix parent_id query - return; + if (legacy()) { + assertAcked(prepareCreate("test") + .setSettings(Settings.builder() + .put(indexSettings()) + .put("index.refresh_interval", -1) + ) + .addMapping("parent") + .addMapping("child", "_parent", "type=parent")); + } else { + assertAcked(prepareCreate("test") + .setSettings(Settings.builder() + .put(indexSettings()) + .put("index.refresh_interval", -1) + ) + .addMapping("doc", "join_field", "type=join,parent=child")); } - - assertAcked(prepareCreate("test") - .setSettings(Settings.builder() - .put(indexSettings()) - .put("index.refresh_interval", -1) - ) - .addMapping("parent") - .addMapping("child", "_parent", "type=parent")); ensureGreen(); - client().prepareIndex("test", "child", "c1").setSource("{}", XContentType.JSON).setParent("p1").get(); + createIndexRequest("test", "child", "c1", "p1").get(); refresh(); SearchResponse response = client().prepareSearch("test").setQuery(parentId("child", "p1")).get(); assertHitCount(response, 1L); - client().prepareIndex("test", "child", "c2").setSource("{}", XContentType.JSON).setParent("p2").get(); + createIndexRequest("test", "child", "c2", "p2").get(); refresh(); response = client().prepareSearch("test") diff --git a/modules/parent-join/src/test/java/org/elasticsearch/join/query/HasChildQueryBuilderTests.java b/modules/parent-join/src/test/java/org/elasticsearch/join/query/HasChildQueryBuilderTests.java index 4b0a8e6781d14..9115bdb41d848 100644 --- a/modules/parent-join/src/test/java/org/elasticsearch/join/query/HasChildQueryBuilderTests.java +++ b/modules/parent-join/src/test/java/org/elasticsearch/join/query/HasChildQueryBuilderTests.java @@ -84,6 +84,14 @@ protected Collection> getPlugins() { return Collections.singletonList(ParentJoinPlugin.class); } + @Override + protected Settings indexSettings() { + return Settings.builder() + .put(super.indexSettings()) + .put("index.mapping.single_type", false) + .build(); + } + @Override protected void initializeAdditionalMappings(MapperService mapperService) throws IOException { similarity = randomFrom("classic", "BM25"); diff --git a/modules/parent-join/src/test/java/org/elasticsearch/join/query/HasParentQueryBuilderTests.java b/modules/parent-join/src/test/java/org/elasticsearch/join/query/HasParentQueryBuilderTests.java index c7b14d19e4f1c..4c3194e31ab79 100644 --- a/modules/parent-join/src/test/java/org/elasticsearch/join/query/HasParentQueryBuilderTests.java +++ b/modules/parent-join/src/test/java/org/elasticsearch/join/query/HasParentQueryBuilderTests.java @@ -25,6 +25,7 @@ import org.elasticsearch.Version; import org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest; import org.elasticsearch.common.compress.CompressedXContent; +import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentFactory; @@ -70,6 +71,14 @@ protected Collection> getPlugins() { return Collections.singletonList(ParentJoinPlugin.class); } + @Override + protected Settings indexSettings() { + return Settings.builder() + .put(super.indexSettings()) + .put("index.mapping.single_type", false) + .build(); + } + @Override protected void initializeAdditionalMappings(MapperService mapperService) throws IOException { // TODO: use a single type when inner hits have been changed to work with join field, diff --git a/core/src/test/java/org/elasticsearch/index/query/ParentIdQueryBuilderTests.java b/modules/parent-join/src/test/java/org/elasticsearch/join/query/LegacyParentIdQueryBuilderTests.java similarity index 83% rename from core/src/test/java/org/elasticsearch/index/query/ParentIdQueryBuilderTests.java rename to modules/parent-join/src/test/java/org/elasticsearch/join/query/LegacyParentIdQueryBuilderTests.java index dc220df2d0329..5342e6b0c5d1d 100644 --- a/core/src/test/java/org/elasticsearch/index/query/ParentIdQueryBuilderTests.java +++ b/modules/parent-join/src/test/java/org/elasticsearch/join/query/LegacyParentIdQueryBuilderTests.java @@ -17,7 +17,7 @@ * under the License. */ -package org.elasticsearch.index.query; +package org.elasticsearch.join.query; import org.apache.lucene.search.BooleanQuery; import org.apache.lucene.search.DocValuesTermsQuery; @@ -26,23 +26,42 @@ import org.apache.lucene.search.TermQuery; import org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest; import org.elasticsearch.common.compress.CompressedXContent; +import org.elasticsearch.common.settings.Settings; import org.elasticsearch.index.mapper.MapperService; import org.elasticsearch.index.mapper.TypeFieldMapper; +import org.elasticsearch.index.query.QueryShardException; +import org.elasticsearch.join.ParentJoinPlugin; +import org.elasticsearch.plugins.Plugin; import org.elasticsearch.search.internal.SearchContext; import org.elasticsearch.test.AbstractQueryTestCase; import org.hamcrest.Matchers; import java.io.IOException; +import java.util.Collection; +import java.util.Collections; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.CoreMatchers.notNullValue; -public class ParentIdQueryBuilderTests extends AbstractQueryTestCase { +public class LegacyParentIdQueryBuilderTests extends AbstractQueryTestCase { protected static final String PARENT_TYPE = "parent"; protected static final String CHILD_TYPE = "child"; + @Override + protected Collection> getPlugins() { + return Collections.singletonList(ParentJoinPlugin.class); + } + + @Override + protected Settings indexSettings() { + return Settings.builder() + .put(super.indexSettings()) + .put("index.mapping.single_type", false) + .build(); + } + @Override protected void initializeAdditionalMappings(MapperService mapperService) throws IOException { mapperService.merge(PARENT_TYPE, new CompressedXContent(PutMappingRequest.buildFromSimplifiedDef(PARENT_TYPE, @@ -84,25 +103,6 @@ protected void doAssertLuceneQuery(ParentIdQueryBuilder queryBuilder, Query quer assertThat(typeQuery.getTerm().text(), Matchers.equalTo(queryBuilder.getType())); } - public void testFromJson() throws IOException { - String query = - "{\n" + - " \"parent_id\" : {\n" + - " \"type\" : \"child\",\n" + - " \"id\" : \"123\",\n" + - " \"ignore_unmapped\" : false,\n" + - " \"boost\" : 3.0,\n" + - " \"_name\" : \"name\"" + - " }\n" + - "}"; - ParentIdQueryBuilder queryBuilder = (ParentIdQueryBuilder) parseQuery(query); - checkGeneratedJson(query, queryBuilder); - assertThat(queryBuilder.getType(), Matchers.equalTo("child")); - assertThat(queryBuilder.getId(), Matchers.equalTo("123")); - assertThat(queryBuilder.boost(), Matchers.equalTo(3f)); - assertThat(queryBuilder.queryName(), Matchers.equalTo("name")); - } - public void testIgnoreUnmapped() throws IOException { final ParentIdQueryBuilder queryBuilder = new ParentIdQueryBuilder("unmapped", "foo"); queryBuilder.ignoreUnmapped(true); diff --git a/modules/parent-join/src/test/java/org/elasticsearch/join/query/ParentIdQueryBuilderTests.java b/modules/parent-join/src/test/java/org/elasticsearch/join/query/ParentIdQueryBuilderTests.java new file mode 100644 index 0000000000000..43d036458b492 --- /dev/null +++ b/modules/parent-join/src/test/java/org/elasticsearch/join/query/ParentIdQueryBuilderTests.java @@ -0,0 +1,134 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.join.query; + +import org.apache.lucene.index.Term; +import org.apache.lucene.search.BooleanClause; +import org.apache.lucene.search.BooleanQuery; +import org.apache.lucene.search.DocValuesTermsQuery; +import org.apache.lucene.search.MatchNoDocsQuery; +import org.apache.lucene.search.Query; +import org.apache.lucene.search.TermQuery; +import org.elasticsearch.Version; +import org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest; +import org.elasticsearch.cluster.metadata.IndexMetaData; +import org.elasticsearch.common.compress.CompressedXContent; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.index.mapper.MapperService; +import org.elasticsearch.index.mapper.TypeFieldMapper; +import org.elasticsearch.index.query.QueryShardException; +import org.elasticsearch.join.ParentJoinPlugin; +import org.elasticsearch.plugins.Plugin; +import org.elasticsearch.search.internal.SearchContext; +import org.elasticsearch.test.AbstractQueryTestCase; +import org.hamcrest.Matchers; + +import java.io.IOException; +import java.util.Collection; +import java.util.Collections; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.CoreMatchers.notNullValue; + +public class ParentIdQueryBuilderTests extends AbstractQueryTestCase { + + private static final String TYPE = "doc"; + private static final String JOIN_FIELD_NAME = "join_field"; + private static final String PARENT_NAME = "parent"; + private static final String CHILD_NAME = "child"; + + @Override + protected Collection> getPlugins() { + return Collections.singletonList(ParentJoinPlugin.class); + } + + @Override + protected Settings indexSettings() { + return Settings.builder() + .put(super.indexSettings()) + .put(IndexMetaData.SETTING_VERSION_CREATED, Version.CURRENT) + .build(); + } + + @Override + protected void initializeAdditionalMappings(MapperService mapperService) throws IOException { + mapperService.merge(TYPE, new CompressedXContent(PutMappingRequest.buildFromSimplifiedDef(TYPE, + STRING_FIELD_NAME, "type=text", + INT_FIELD_NAME, "type=integer", + DOUBLE_FIELD_NAME, "type=double", + BOOLEAN_FIELD_NAME, "type=boolean", + DATE_FIELD_NAME, "type=date", + OBJECT_FIELD_NAME, "type=object", + JOIN_FIELD_NAME, "type=join,parent=child" + ).string()), MapperService.MergeReason.MAPPING_UPDATE, false); + } + + @Override + protected ParentIdQueryBuilder doCreateTestQueryBuilder() { + return new ParentIdQueryBuilder(CHILD_NAME, randomAlphaOfLength(4)).ignoreUnmapped(randomBoolean()); + } + + @Override + protected void doAssertLuceneQuery(ParentIdQueryBuilder queryBuilder, Query query, SearchContext context) throws IOException { + assertThat(query, Matchers.instanceOf(BooleanQuery.class)); + BooleanQuery booleanQuery = (BooleanQuery) query; + assertThat(booleanQuery.clauses().size(), Matchers.equalTo(2)); + BooleanQuery expected = new BooleanQuery.Builder() + .add(new TermQuery(new Term(JOIN_FIELD_NAME + "#" + PARENT_NAME, queryBuilder.getId())), BooleanClause.Occur.MUST) + .add(new TermQuery(new Term(JOIN_FIELD_NAME, queryBuilder.getType())), BooleanClause.Occur.FILTER) + .build(); + assertThat(expected, equalTo(query)); + } + + public void testFromJson() throws IOException { + String query = + "{\n" + + " \"parent_id\" : {\n" + + " \"type\" : \"child\",\n" + + " \"id\" : \"123\",\n" + + " \"ignore_unmapped\" : false,\n" + + " \"boost\" : 3.0,\n" + + " \"_name\" : \"name\"" + + " }\n" + + "}"; + ParentIdQueryBuilder queryBuilder = (ParentIdQueryBuilder) parseQuery(query); + checkGeneratedJson(query, queryBuilder); + assertThat(queryBuilder.getType(), Matchers.equalTo("child")); + assertThat(queryBuilder.getId(), Matchers.equalTo("123")); + assertThat(queryBuilder.boost(), Matchers.equalTo(3f)); + assertThat(queryBuilder.queryName(), Matchers.equalTo("name")); + } + + public void testIgnoreUnmapped() throws IOException { + final ParentIdQueryBuilder queryBuilder = new ParentIdQueryBuilder("unmapped", "foo"); + queryBuilder.ignoreUnmapped(true); + Query query = queryBuilder.toQuery(createShardContext()); + assertThat(query, notNullValue()); + assertThat(query, instanceOf(MatchNoDocsQuery.class)); + + final ParentIdQueryBuilder failingQueryBuilder = new ParentIdQueryBuilder("unmapped", "foo"); + failingQueryBuilder.ignoreUnmapped(false); + QueryShardException e = expectThrows(QueryShardException.class, () -> failingQueryBuilder.toQuery(createShardContext())); + assertThat(e.getMessage(), containsString("[" + ParentIdQueryBuilder.NAME + "] no relation found for child [unmapped]")); + } + +} diff --git a/modules/parent-join/src/test/resources/rest-api-spec/test/20_parent_join.yml b/modules/parent-join/src/test/resources/rest-api-spec/test/20_parent_join.yml index 97af63ab00722..8f6907e323716 100644 --- a/modules/parent-join/src/test/resources/rest-api-spec/test/20_parent_join.yml +++ b/modules/parent-join/src/test/resources/rest-api-spec/test/20_parent_join.yml @@ -20,6 +20,13 @@ setup: index: test type: doc id: 2 + body: { "join_field": { "name": "parent" } } + + - do: + index: + index: test + type: doc + id: 3 routing: 1 body: { "join_field": { "name": "child", "parent": "1" } } @@ -27,9 +34,25 @@ setup: index: index: test type: doc - id: 3 + id: 4 + routing: 1 + body: { "join_field": { "name": "child", "parent": "1" } } + + - do: + index: + index: test + type: doc + id: 5 + routing: 1 + body: { "join_field": { "name": "child", "parent": "2" } } + + - do: + index: + index: test + type: doc + id: 6 routing: 1 - body: { "join_field": { "name": "grand_child", "parent": "2" } } + body: { "join_field": { "name": "grand_child", "parent": "5" } } - do: indices.refresh: {} @@ -42,24 +65,68 @@ setup: - do: search: - body: { sort: ["join_field"] } + body: { sort: ["join_field", "_id"] } - - match: { hits.total: 3 } + - match: { hits.total: 6 } - match: { hits.hits.0._index: "test" } - match: { hits.hits.0._type: "doc" } - - match: { hits.hits.0._id: "2" } + - match: { hits.hits.0._id: "3" } - match: { hits.hits.0.fields.join_field: ["child"] } - match: { hits.hits.0.fields.join_field#parent: ["1"] } - is_false: hits.hits.0.fields.join_field#child } - match: { hits.hits.1._index: "test" } - match: { hits.hits.1._type: "doc" } - - match: { hits.hits.1._id: "3" } - - match: { hits.hits.1.fields.join_field: ["grand_child"] } - - match: { hits.hits.1.fields.join_field#child: ["2"] } + - match: { hits.hits.1._id: "4" } + - match: { hits.hits.1.fields.join_field: ["child"] } + - match: { hits.hits.1.fields.join_field#parent: ["1"] } + - is_false: hits.hits.1.fields.join_field#child } - match: { hits.hits.2._index: "test" } - match: { hits.hits.2._type: "doc" } - - match: { hits.hits.2._id: "1" } - - match: { hits.hits.2.fields.join_field: ["parent"] } - - is_false: hits.hits.2.fields.join_field#parent + - match: { hits.hits.2._id: "5" } + - match: { hits.hits.2.fields.join_field: ["child"] } + - match: { hits.hits.2.fields.join_field#parent: ["2"] } + - is_false: hits.hits.2.fields.join_field#child } + - match: { hits.hits.3._index: "test" } + - match: { hits.hits.3._type: "doc" } + - match: { hits.hits.3._id: "6" } + - match: { hits.hits.3.fields.join_field: ["grand_child"] } + - match: { hits.hits.3.fields.join_field#child: ["5"] } + - match: { hits.hits.4._index: "test" } + - match: { hits.hits.4._type: "doc" } + - match: { hits.hits.4._id: "1" } + - match: { hits.hits.4.fields.join_field: ["parent"] } + - is_false: hits.hits.4.fields.join_field#parent + - match: { hits.hits.5._index: "test" } + - match: { hits.hits.5._type: "doc" } + - match: { hits.hits.5._id: "2" } + - match: { hits.hits.5.fields.join_field: ["parent"] } + - is_false: hits.hits.5.fields.join_field#parent + +--- +"Test parent_id query": + - skip: + version: " - 5.99.99" + reason: parent-join was added in 6.0 + + - do: + search: + body: + sort: [ "_id" ] + query: + parent_id: + type: child + id: 1 + + - match: { hits.total: 2 } + - match: { hits.hits.0._index: "test" } + - match: { hits.hits.0._type: "doc" } + - match: { hits.hits.0._id: "3" } + - match: { hits.hits.0.fields.join_field: ["child"] } + - match: { hits.hits.0.fields.join_field#parent: ["1"] } + - match: { hits.hits.1._index: "test" } + - match: { hits.hits.1._type: "doc" } + - match: { hits.hits.1._id: "4" } + - match: { hits.hits.1.fields.join_field: ["child"] } + - match: { hits.hits.1.fields.join_field#parent: ["1"] } diff --git a/modules/percolator/src/test/java/org/elasticsearch/percolator/PercolateQueryBuilderTests.java b/modules/percolator/src/test/java/org/elasticsearch/percolator/PercolateQueryBuilderTests.java index d811a26cb8e35..dd518ba143894 100644 --- a/modules/percolator/src/test/java/org/elasticsearch/percolator/PercolateQueryBuilderTests.java +++ b/modules/percolator/src/test/java/org/elasticsearch/percolator/PercolateQueryBuilderTests.java @@ -86,8 +86,8 @@ protected Collection> getPlugins() { @Override protected void initializeAdditionalMappings(MapperService mapperService) throws IOException { queryField = randomAlphaOfLength(4); - docType = randomAlphaOfLength(4); - mapperService.merge("query_type", new CompressedXContent(PutMappingRequest.buildFromSimplifiedDef("query_type", + docType = "doc"; + mapperService.merge("doc", new CompressedXContent(PutMappingRequest.buildFromSimplifiedDef("doc", queryField, "type=percolator" ).string()), MapperService.MergeReason.MAPPING_UPDATE, false); mapperService.merge(docType, new CompressedXContent(PutMappingRequest.buildFromSimplifiedDef(docType, @@ -104,7 +104,7 @@ private PercolateQueryBuilder doCreateTestQueryBuilder(boolean indexedDocument) documentSource = randomSource(); if (indexedDocument) { indexedDocumentIndex = randomAlphaOfLength(4); - indexedDocumentType = randomAlphaOfLength(4); + indexedDocumentType = "doc"; indexedDocumentId = randomAlphaOfLength(4); indexedDocumentRouting = randomAlphaOfLength(4); indexedDocumentPreference = randomAlphaOfLength(4); diff --git a/test/framework/src/main/java/org/elasticsearch/test/AbstractQueryTestCase.java b/test/framework/src/main/java/org/elasticsearch/test/AbstractQueryTestCase.java index 3f44c24494232..ee00da25f8a6b 100644 --- a/test/framework/src/main/java/org/elasticsearch/test/AbstractQueryTestCase.java +++ b/test/framework/src/main/java/org/elasticsearch/test/AbstractQueryTestCase.java @@ -150,7 +150,6 @@ public abstract class AbstractQueryTestCase> private static ServiceHolder serviceHolder; private static int queryNameId = 0; private static Settings nodeSettings; - private static Settings indexSettings; private static Index index; private static String[] currentTypes; private static String[] randomTypes; @@ -172,29 +171,27 @@ protected void initializeAdditionalMappings(MapperService mapperService) throws @BeforeClass public static void beforeClass() { - // we have to prefer CURRENT since with the range of versions we support it's rather unlikely to get the current actually. - Version indexVersionCreated = randomBoolean() ? Version.CURRENT - : VersionUtils.randomVersionBetween(random(), null, Version.CURRENT); nodeSettings = Settings.builder() .put("node.name", AbstractQueryTestCase.class.toString()) .put(Environment.PATH_HOME_SETTING.getKey(), createTempDir()) .build(); - indexSettings = Settings.builder() - .put(IndexMetaData.SETTING_VERSION_CREATED, indexVersionCreated) - .put("index.mapping.single_type", false).build(); index = new Index(randomAlphaOfLengthBetween(1, 10), "_na_"); - //create some random type with some default field, those types will stick around for all of the subclasses - currentTypes = new String[randomIntBetween(0, 5)]; - for (int i = 0; i < currentTypes.length; i++) { - String type = randomAlphaOfLengthBetween(1, 10); - currentTypes[i] = type; - } - //set some random types to be queried as part the search request, before each test + // Set a single type in the index + currentTypes = new String[] { "doc" }; randomTypes = getRandomTypes(); } + protected Settings indexSettings() { + // we have to prefer CURRENT since with the range of versions we support it's rather unlikely to get the current actually. + Version indexVersionCreated = randomBoolean() ? Version.CURRENT + : VersionUtils.randomVersionBetween(random(), null, Version.CURRENT); + return Settings.builder() + .put(IndexMetaData.SETTING_VERSION_CREATED, indexVersionCreated) + .build(); + } + @AfterClass public static void afterClass() throws Exception { IOUtils.close(serviceHolder); @@ -204,7 +201,7 @@ public static void afterClass() throws Exception { @Before public void beforeTest() throws IOException { if (serviceHolder == null) { - serviceHolder = new ServiceHolder(nodeSettings, indexSettings, getPlugins(), this); + serviceHolder = new ServiceHolder(nodeSettings, indexSettings(), getPlugins(), this); } serviceHolder.clientInvocationHandler.delegate = this; } @@ -855,10 +852,6 @@ private static String[] getRandomTypes() { return types; } - protected static String getRandomType() { - return (currentTypes.length == 0) ? MetaData.ALL : randomFrom(currentTypes); - } - protected static Fuzziness randomFuzziness(String fieldName) { switch (fieldName) { case INT_FIELD_NAME: