Prechádzať zdrojové kódy

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
Jim Ferenczi 8 rokov pred
rodič
commit
7e60cf3e54
21 zmenil súbory, kde vykonal 360 pridanie a 98 odobranie
  1. 0 8
      core/src/main/java/org/elasticsearch/index/query/QueryBuilders.java
  2. 0 2
      core/src/main/java/org/elasticsearch/search/SearchModule.java
  3. 1 1
      core/src/test/java/org/elasticsearch/index/query/MatchQueryBuilderTests.java
  4. 1 1
      core/src/test/java/org/elasticsearch/index/query/MoreLikeThisQueryBuilderTests.java
  5. 1 1
      core/src/test/java/org/elasticsearch/index/query/NestedQueryBuilderTests.java
  6. 4 4
      core/src/test/java/org/elasticsearch/index/query/QueryStringQueryBuilderTests.java
  7. 1 1
      core/src/test/java/org/elasticsearch/index/query/SpanTermQueryBuilderTests.java
  8. 11 2
      core/src/test/java/org/elasticsearch/index/query/TypeQueryBuilderTests.java
  9. 2 2
      core/src/test/java/org/elasticsearch/index/query/WildcardQueryBuilderTests.java
  10. 0 1
      core/src/test/java/org/elasticsearch/search/SearchModuleTests.java
  11. 3 1
      modules/parent-join/src/main/java/org/elasticsearch/join/ParentJoinPlugin.java
  12. 7 0
      modules/parent-join/src/main/java/org/elasticsearch/join/query/JoinQueryBuilders.java
  13. 46 6
      modules/parent-join/src/main/java/org/elasticsearch/join/query/ParentIdQueryBuilder.java
  14. 18 14
      modules/parent-join/src/test/java/org/elasticsearch/join/query/ChildQuerySearchIT.java
  15. 8 0
      modules/parent-join/src/test/java/org/elasticsearch/join/query/HasChildQueryBuilderTests.java
  16. 9 0
      modules/parent-join/src/test/java/org/elasticsearch/join/query/HasParentQueryBuilderTests.java
  17. 21 21
      modules/parent-join/src/test/java/org/elasticsearch/join/query/LegacyParentIdQueryBuilderTests.java
  18. 134 0
      modules/parent-join/src/test/java/org/elasticsearch/join/query/ParentIdQueryBuilderTests.java
  19. 78 11
      modules/parent-join/src/test/resources/rest-api-spec/test/20_parent_join.yml
  20. 3 3
      modules/percolator/src/test/java/org/elasticsearch/percolator/PercolateQueryBuilderTests.java
  21. 12 19
      test/framework/src/main/java/org/elasticsearch/test/AbstractQueryTestCase.java

+ 0 - 8
core/src/main/java/org/elasticsearch/index/query/QueryBuilders.java

@@ -471,14 +471,6 @@ public abstract class QueryBuilders {
         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);
     }

+ 0 - 2
core/src/main/java/org/elasticsearch/search/SearchModule.java

@@ -52,7 +52,6 @@ import org.elasticsearch.index.query.MatchQueryBuilder;
 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 @@ public class SearchModule {
         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));

+ 1 - 1
core/src/test/java/org/elasticsearch/index/query/MatchQueryBuilderTests.java

@@ -430,7 +430,7 @@ public class MatchQueryBuilderTests extends AbstractQueryTestCase<MatchQueryBuil
 
     @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);
     }
 

+ 1 - 1
core/src/test/java/org/elasticsearch/index/query/MoreLikeThisQueryBuilderTests.java

@@ -97,7 +97,7 @@ public class MoreLikeThisQueryBuilderTests extends AbstractQueryTestCase<MoreLik
 
     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()) {

+ 1 - 1
core/src/test/java/org/elasticsearch/index/query/NestedQueryBuilderTests.java

@@ -55,7 +55,7 @@ public class NestedQueryBuilderTests extends AbstractQueryTestCase<NestedQueryBu
 
     @Override
     protected void initializeAdditionalMappings(MapperService mapperService) throws IOException {
-        mapperService.merge("nested_doc", new CompressedXContent(PutMappingRequest.buildFromSimplifiedDef("nested_doc",
+        mapperService.merge("doc", new CompressedXContent(PutMappingRequest.buildFromSimplifiedDef("doc",
                 STRING_FIELD_NAME, "type=text",
                 INT_FIELD_NAME, "type=integer",
                 DOUBLE_FIELD_NAME, "type=double",

+ 4 - 4
core/src/test/java/org/elasticsearch/index/query/QueryStringQueryBuilderTests.java

@@ -858,9 +858,9 @@ public class QueryStringQueryBuilderTests extends AbstractQueryTestCase<QueryStr
 
     public void testDisabledFieldNamesField() throws Exception {
         QueryShardContext context = createShardContext();
-        context.getMapperService().merge("new_type",
+        context.getMapperService().merge("doc",
             new CompressedXContent(
-                PutMappingRequest.buildFromSimplifiedDef("new_type",
+                PutMappingRequest.buildFromSimplifiedDef("doc",
                     "foo", "type=text",
                     "_field_names", "enabled=false").string()),
             MapperService.MergeReason.MAPPING_UPDATE, true);
@@ -868,9 +868,9 @@ public class QueryStringQueryBuilderTests extends AbstractQueryTestCase<QueryStr
         Query query = queryBuilder.toQuery(context);
         Query expected = new WildcardQuery(new Term("foo", "*"));
         assertThat(query, equalTo(expected));
-        context.getMapperService().merge("new_type",
+        context.getMapperService().merge("doc",
             new CompressedXContent(
-                PutMappingRequest.buildFromSimplifiedDef("new_type",
+                PutMappingRequest.buildFromSimplifiedDef("doc",
                     "foo", "type=text",
                     "_field_names", "enabled=true").string()),
             MapperService.MergeReason.MAPPING_UPDATE, true);

+ 1 - 1
core/src/test/java/org/elasticsearch/index/query/SpanTermQueryBuilderTests.java

@@ -133,7 +133,7 @@ public class SpanTermQueryBuilderTests extends AbstractTermQueryTestCase<SpanTer
 
     public void testWithMetaDataField() throws IOException {
         QueryShardContext context = createShardContext();
-        for (String field : new String[]{"_type", "_all"}) {
+        for (String field : new String[]{"field1", "field2"}) {
             SpanTermQueryBuilder spanTermQueryBuilder = new SpanTermQueryBuilder(field, "toto");
             Query query = spanTermQueryBuilder.toQuery(context);
             Query expected = new SpanTermQuery(new Term(field, "toto"));

+ 11 - 2
core/src/test/java/org/elasticsearch/index/query/TypeQueryBuilderTests.java

@@ -19,6 +19,7 @@
 
 package org.elasticsearch.index.query;
 
+import org.apache.lucene.search.MatchAllDocsQuery;
 import org.apache.lucene.search.MatchNoDocsQuery;
 import org.apache.lucene.search.Query;
 import org.apache.lucene.util.BytesRef;
@@ -28,11 +29,15 @@ import org.elasticsearch.test.AbstractQueryTestCase;
 
 import java.io.IOException;
 
+import static org.hamcrest.Matchers.anyOf;
+import static org.hamcrest.Matchers.equalTo;
+
+
 public class TypeQueryBuilderTests extends AbstractQueryTestCase<TypeQueryBuilder> {
 
     @Override
     protected TypeQueryBuilder doCreateTestQueryBuilder() {
-        return new TypeQueryBuilder(getRandomType());
+        return new TypeQueryBuilder("doc");
     }
 
     @Override
@@ -40,7 +45,11 @@ public class TypeQueryBuilderTests extends AbstractQueryTestCase<TypeQueryBuilde
         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()))
+            );
         }
     }
 

+ 2 - 2
core/src/test/java/org/elasticsearch/index/query/WildcardQueryBuilderTests.java

@@ -89,7 +89,7 @@ public class WildcardQueryBuilderTests extends AbstractQueryTestCase<WildcardQue
     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 class WildcardQueryBuilderTests extends AbstractQueryTestCase<WildcardQue
 
     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"));

+ 0 - 1
core/src/test/java/org/elasticsearch/search/SearchModuleTests.java

@@ -279,7 +279,6 @@ public class SearchModuleTests extends ModuleTestCase {
             "more_like_this",
             "multi_match",
             "nested",
-            "parent_id",
             "prefix",
             "query_string",
             "range",

+ 3 - 1
modules/parent-join/src/main/java/org/elasticsearch/join/ParentJoinPlugin.java

@@ -27,6 +27,7 @@ import org.elasticsearch.join.fetch.ParentJoinFieldSubFetchPhase;
 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 class ParentJoinPlugin extends Plugin implements SearchPlugin, MapperPlug
     public List<QuerySpec<?>> 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)
         );
     }
 

+ 7 - 0
modules/parent-join/src/main/java/org/elasticsearch/join/query/JoinQueryBuilders.java

@@ -47,4 +47,11 @@ public abstract class JoinQueryBuilders {
         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);
+    }
 }

+ 46 - 6
core/src/main/java/org/elasticsearch/index/query/ParentIdQueryBuilder.java → 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.common.xcontent.XContentParser;
 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 final class ParentIdQueryBuilder extends AbstractQueryBuilder<ParentIdQue
 
     @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 @@ public final class ParentIdQueryBuilder extends AbstractQueryBuilder<ParentIdQue
         }
         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

+ 18 - 14
modules/parent-join/src/test/java/org/elasticsearch/join/query/ChildQuerySearchIT.java

@@ -77,7 +77,6 @@ import static org.elasticsearch.index.query.QueryBuilders.constantScoreQuery;
 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.
 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 class ChildQuerySearchIT extends ESIntegTestCase {
     }
 
     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")

+ 8 - 0
modules/parent-join/src/test/java/org/elasticsearch/join/query/HasChildQueryBuilderTests.java

@@ -84,6 +84,14 @@ public class HasChildQueryBuilderTests extends AbstractQueryTestCase<HasChildQue
         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");

+ 9 - 0
modules/parent-join/src/test/java/org/elasticsearch/join/query/HasParentQueryBuilderTests.java

@@ -25,6 +25,7 @@ import org.apache.lucene.search.join.ScoreMode;
 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 @@ public class HasParentQueryBuilderTests extends AbstractQueryTestCase<HasParentQ
         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,

+ 21 - 21
core/src/test/java/org/elasticsearch/index/query/ParentIdQueryBuilderTests.java → 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.Query;
 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<ParentIdQueryBuilder> {
+public class LegacyParentIdQueryBuilderTests extends AbstractQueryTestCase<ParentIdQueryBuilder> {
 
     protected static final String PARENT_TYPE = "parent";
     protected static final String CHILD_TYPE = "child";
 
+    @Override
+    protected Collection<Class<? extends Plugin>> 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 @@ public class ParentIdQueryBuilderTests extends AbstractQueryTestCase<ParentIdQue
         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);

+ 134 - 0
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<ParentIdQueryBuilder> {
+
+    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<Class<? extends Plugin>> 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]"));
+    }
+
+}

+ 78 - 11
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"] }
 
 

+ 3 - 3
modules/percolator/src/test/java/org/elasticsearch/percolator/PercolateQueryBuilderTests.java

@@ -86,8 +86,8 @@ public class PercolateQueryBuilderTests extends AbstractQueryTestCase<PercolateQ
     @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 @@ public class PercolateQueryBuilderTests extends AbstractQueryTestCase<PercolateQ
         documentSource = randomSource();
         if (indexedDocument) {
             indexedDocumentIndex = randomAlphaOfLength(4);
-            indexedDocumentType = randomAlphaOfLength(4);
+            indexedDocumentType = "doc";
             indexedDocumentId = randomAlphaOfLength(4);
             indexedDocumentRouting = randomAlphaOfLength(4);
             indexedDocumentPreference = randomAlphaOfLength(4);

+ 12 - 19
test/framework/src/main/java/org/elasticsearch/test/AbstractQueryTestCase.java

@@ -150,7 +150,6 @@ public abstract class AbstractQueryTestCase<QB extends AbstractQueryBuilder<QB>>
     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 @@ public abstract class AbstractQueryTestCase<QB extends AbstractQueryBuilder<QB>>
 
     @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 abstract class AbstractQueryTestCase<QB extends AbstractQueryBuilder<QB>>
     @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 @@ public abstract class AbstractQueryTestCase<QB extends AbstractQueryBuilder<QB>>
         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: