Sfoglia il codice sorgente

Rework parent-join to not require access to DocumentMapper (#63738)

Parent joins work using a cluster of field mappers: the join field itself;
a set of subfields that allow multiple relationships between parents and
children to be defined; and a metadata field that acts to only allow a
single join field per index to be defined. The various queries and
aggregations that use this infrastructure retrieve the join field mapper
via a static method and then build themselves by pulling individual
relationship mappers from this main mapper.

Using mappers rather than MappedFieldTypes means that we need to
expose DocumentMapper at search time, which is something we are
trying to avoid. This commit refactors things so that the join relations
are encapsulated in a Joiner object, which lives instead on the
MappedFieldType associated with the metadata join field. Rather than
using the ParentJoinFieldMapper and connected ParentIdFieldMappers,
we can now build queries and aggregations using this Joiner object,
retrieved via the QueryShardContext or AggregationContext using
a static helper method on Joiner itself.
Alan Woodward 5 anni fa
parent
commit
70d88ef62d
18 ha cambiato i file con 461 aggiunte e 354 eliminazioni
  1. 6 14
      modules/parent-join/src/main/java/org/elasticsearch/join/aggregations/ChildrenAggregationBuilder.java
  2. 6 8
      modules/parent-join/src/main/java/org/elasticsearch/join/aggregations/ParentAggregationBuilder.java
  3. 203 0
      modules/parent-join/src/main/java/org/elasticsearch/join/mapper/Joiner.java
  4. 8 62
      modules/parent-join/src/main/java/org/elasticsearch/join/mapper/ParentIdFieldMapper.java
  5. 45 136
      modules/parent-join/src/main/java/org/elasticsearch/join/mapper/ParentJoinFieldMapper.java
  6. 56 0
      modules/parent-join/src/main/java/org/elasticsearch/join/mapper/Relations.java
  7. 22 16
      modules/parent-join/src/main/java/org/elasticsearch/join/query/HasChildQueryBuilder.java
  8. 26 29
      modules/parent-join/src/main/java/org/elasticsearch/join/query/HasParentQueryBuilder.java
  9. 13 18
      modules/parent-join/src/main/java/org/elasticsearch/join/query/ParentChildInnerHitContextBuilder.java
  10. 8 8
      modules/parent-join/src/main/java/org/elasticsearch/join/query/ParentIdQueryBuilder.java
  11. 5 8
      modules/parent-join/src/test/java/org/elasticsearch/join/aggregations/ChildrenToParentAggregatorTests.java
  12. 5 8
      modules/parent-join/src/test/java/org/elasticsearch/join/aggregations/ParentToChildrenAggregatorTests.java
  13. 41 31
      modules/parent-join/src/test/java/org/elasticsearch/join/mapper/ParentJoinFieldMapperTests.java
  14. 6 5
      modules/parent-join/src/test/java/org/elasticsearch/join/query/HasChildQueryBuilderTests.java
  15. 3 2
      modules/parent-join/src/test/java/org/elasticsearch/join/query/HasParentQueryBuilderTests.java
  16. 4 5
      server/src/main/java/org/elasticsearch/search/aggregations/support/AggregationContext.java
  17. 1 1
      server/src/main/java/org/elasticsearch/search/fetch/subphase/highlight/CustomQueryScorer.java
  18. 3 3
      test/framework/src/main/java/org/elasticsearch/index/mapper/MapperServiceTestCase.java

+ 6 - 14
modules/parent-join/src/main/java/org/elasticsearch/join/aggregations/ChildrenAggregationBuilder.java

@@ -26,8 +26,7 @@ import org.elasticsearch.common.io.stream.StreamOutput;
 import org.elasticsearch.common.xcontent.XContentBuilder;
 import org.elasticsearch.common.xcontent.XContentParser;
 import org.elasticsearch.index.mapper.MappedFieldType;
-import org.elasticsearch.join.mapper.ParentIdFieldMapper;
-import org.elasticsearch.join.mapper.ParentJoinFieldMapper;
+import org.elasticsearch.join.mapper.Joiner;
 import org.elasticsearch.search.aggregations.AggregationBuilder;
 import org.elasticsearch.search.aggregations.AggregatorFactories.Builder;
 import org.elasticsearch.search.aggregations.AggregatorFactory;
@@ -113,23 +112,16 @@ public class ChildrenAggregationBuilder extends ValuesSourceAggregationBuilder<C
     protected ValuesSourceConfig resolveConfig(AggregationContext context) {
         ValuesSourceConfig config;
 
-        ParentJoinFieldMapper parentJoinFieldMapper = ParentJoinFieldMapper.getMapper(context::getFieldType, context::getMapper);
-        if (parentJoinFieldMapper == null) {
+        Joiner joiner = Joiner.getJoiner(context);
+        if (joiner == null || joiner.childTypeExists(childType) == false) {
             // Unmapped field case
             config = ValuesSourceConfig.resolveUnmapped(defaultValueSourceType(), context);
             return config;
         }
 
-        ParentIdFieldMapper parentIdFieldMapper = parentJoinFieldMapper.getParentIdFieldMapper(childType, false);
-        if (parentIdFieldMapper == null) {
-            // Unmapped field case
-            config = ValuesSourceConfig.resolveUnmapped(defaultValueSourceType(), context);
-            return config;
-        }
-
-        parentFilter = parentIdFieldMapper.getParentFilter();
-        childFilter = parentIdFieldMapper.getChildFilter(childType);
-        MappedFieldType fieldType = parentIdFieldMapper.fieldType();
+        parentFilter = joiner.parentFilter(childType);
+        childFilter = joiner.filter(childType);
+        MappedFieldType fieldType = context.getFieldType(joiner.parentJoinField(childType));
         config = ValuesSourceConfig.resolveFieldOnly(fieldType, context);
         return config;
     }

+ 6 - 8
modules/parent-join/src/main/java/org/elasticsearch/join/aggregations/ParentAggregationBuilder.java

@@ -26,8 +26,7 @@ import org.elasticsearch.common.io.stream.StreamOutput;
 import org.elasticsearch.common.xcontent.XContentBuilder;
 import org.elasticsearch.common.xcontent.XContentParser;
 import org.elasticsearch.index.mapper.MappedFieldType;
-import org.elasticsearch.join.mapper.ParentIdFieldMapper;
-import org.elasticsearch.join.mapper.ParentJoinFieldMapper;
+import org.elasticsearch.join.mapper.Joiner;
 import org.elasticsearch.search.aggregations.AggregationBuilder;
 import org.elasticsearch.search.aggregations.AggregatorFactories.Builder;
 import org.elasticsearch.search.aggregations.AggregatorFactory;
@@ -113,12 +112,11 @@ public class ParentAggregationBuilder extends ValuesSourceAggregationBuilder<Par
     @Override
     protected ValuesSourceConfig resolveConfig(AggregationContext context) {
         ValuesSourceConfig config;
-        ParentJoinFieldMapper parentJoinFieldMapper = ParentJoinFieldMapper.getMapper(context::getFieldType, context::getMapper);
-        ParentIdFieldMapper parentIdFieldMapper = parentJoinFieldMapper.getParentIdFieldMapper(childType, false);
-        if (parentIdFieldMapper != null) {
-            parentFilter = parentIdFieldMapper.getParentFilter();
-            childFilter = parentIdFieldMapper.getChildFilter(childType);
-            MappedFieldType fieldType = parentIdFieldMapper.fieldType();
+        Joiner joiner = Joiner.getJoiner(context);
+        if (joiner != null && joiner.childTypeExists(childType)) {
+            parentFilter = joiner.parentFilter(childType);
+            childFilter = joiner.filter(childType);
+            MappedFieldType fieldType = context.getFieldType(joiner.parentJoinField(childType));
             config = ValuesSourceConfig.resolveFieldOnly(fieldType, context);
         } else {
             // unmapped case

+ 203 - 0
modules/parent-join/src/main/java/org/elasticsearch/join/mapper/Joiner.java

@@ -0,0 +1,203 @@
+/*
+ * 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.mapper;
+
+import org.apache.lucene.index.Term;
+import org.apache.lucene.search.BooleanClause;
+import org.apache.lucene.search.BooleanQuery;
+import org.apache.lucene.search.ConstantScoreQuery;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.TermQuery;
+import org.elasticsearch.index.mapper.MappedFieldType;
+import org.elasticsearch.index.query.QueryShardContext;
+import org.elasticsearch.search.aggregations.support.AggregationContext;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Predicate;
+
+/**
+ * Utility class to help build join queries and aggregations, based on a join_field
+ */
+public final class Joiner {
+
+    /**
+     * Get the Joiner for this context, or {@code null} if none is configured
+     */
+    public static Joiner getJoiner(QueryShardContext context) {
+        return getJoiner(context::isFieldMapped, context::getFieldType);
+    }
+
+    /**
+     * Get the Joiner for this context, or {@code null} if none is configured
+     */
+    public static Joiner getJoiner(AggregationContext context) {
+        return getJoiner(context::isFieldMapped, context::getFieldType);
+    }
+
+    /**
+     * Get the Joiner for this context, or {@code null} if none is configured
+     */
+    static Joiner getJoiner(Predicate<String> isMapped, Function<String, MappedFieldType> getFieldType) {
+        if (isMapped.test(MetaJoinFieldMapper.NAME) == false) {
+            return null;
+        }
+        MetaJoinFieldMapper.MetaJoinFieldType ft
+            = (MetaJoinFieldMapper.MetaJoinFieldType) getFieldType.apply(MetaJoinFieldMapper.NAME);
+        String joinField = ft.getJoinField();
+        if (isMapped.test(joinField) == false) {
+            return null;
+        }
+        ParentJoinFieldMapper.JoinFieldType jft =
+            (ParentJoinFieldMapper.JoinFieldType) getFieldType.apply(joinField);
+        return jft.getJoiner();
+    }
+
+    private final Map<String, Set<String>> parentsToChildren = new HashMap<>();
+    private final Map<String, String> childrenToParents = new HashMap<>();
+
+    private final String joinField;
+
+    /**
+     * Constructs a Joiner based on a join field and a set of relations
+     */
+    Joiner(String joinField, List<Relations> relations) {
+        this.joinField = joinField;
+        for (Relations r : relations) {
+            for (String child : r.children) {
+                parentsToChildren.put(r.parent, r.children);
+                if (childrenToParents.containsKey(child)) {
+                    throw new IllegalArgumentException("[" + child + "] cannot have multiple parents");
+                }
+                childrenToParents.put(child, r.parent);
+            }
+        }
+    }
+
+    /**
+     * @return the join field for the index
+     */
+    public String getJoinField() {
+        return joinField;
+    }
+
+    /**
+     * @return a filter for documents of a specific join type
+     */
+    public Query filter(String relationType) {
+        return new TermQuery(new Term(joinField, relationType));
+    }
+
+    /**
+     * @return a filter for parent documents of a specific child type
+     */
+    public Query parentFilter(String childType) {
+        return new TermQuery(new Term(joinField, childrenToParents.get(childType)));
+    }
+
+    /**
+     * @return a filter for child documents of a specific parent type
+     */
+    public Query childrenFilter(String parentType) {
+        assert parentTypeExists(parentType);
+        BooleanQuery.Builder builder = new BooleanQuery.Builder();
+        for (String child : parentsToChildren.get(parentType)) {
+            builder.add(filter(child), BooleanClause.Occur.SHOULD);
+        }
+        return new ConstantScoreQuery(builder.build());
+    }
+
+    /**
+     * @return {@code true} if the child type has been defined for the join field
+     */
+    public boolean childTypeExists(String type) {
+        return childrenToParents.containsKey(type);
+    }
+
+    /**
+     * @return {@code true} if the parent type has been defined for the join field
+     */
+    public boolean parentTypeExists(String type) {
+        return parentsToChildren.containsKey(type);
+    }
+
+    /**
+     * @return {@code true} if the type has been defined as either a parent
+     * or a child for the join field
+     */
+    public boolean knownRelation(String type) {
+        return childTypeExists(type) || parentTypeExists(type);
+    }
+
+    /**
+     * @return the name of the linked join field for documents of a specific child type
+     */
+    public String parentJoinField(String childType) {
+        return joinField + "#" + childrenToParents.get(childType);
+    }
+
+    /**
+     * @return the name of the linked join field for documents of a specific parent type
+     */
+    public String childJoinField(String parentType) {
+        return joinField + "#" + parentType;
+    }
+
+    boolean canMerge(Joiner other, Consumer<String> conflicts) {
+        boolean conflicted = false;
+        for (String parent : parentsToChildren.keySet()) {
+            if (other.parentsToChildren.containsKey(parent) == false) {
+                conflicts.accept("Cannot remove parent [" + parent + "]");
+                conflicted = true;
+            }
+        }
+        for (String child : childrenToParents.keySet()) {
+            if (other.childrenToParents.containsKey(child) == false) {
+                conflicts.accept("Cannot remove child [" + child + "]");
+                conflicted = true;
+            }
+        }
+        for (String newParent : other.parentsToChildren.keySet()) {
+            if (childrenToParents.containsKey(newParent) && parentsToChildren.containsKey(newParent) == false) {
+                conflicts.accept("Cannot create parent [" + newParent + "] from an existing child");
+                conflicted = true;
+            }
+        }
+        for (String newChild : other.childrenToParents.keySet()) {
+            if (this.childrenToParents.containsKey(newChild)
+                && Objects.equals(other.childrenToParents.get(newChild), this.childrenToParents.get(newChild)) == false) {
+                conflicts.accept("Cannot change parent of [" + newChild + "]");
+                conflicted = true;
+            }
+            if (this.parentsToChildren.containsKey(newChild)
+                && Objects.equals(this.childrenToParents.get(newChild), other.childrenToParents.get(newChild)) == false) {
+                conflicts.accept("Cannot create child [" + newChild + "] from an existing root");
+                conflicted = true;
+            }
+        }
+        return conflicted == false;
+    }
+
+}

+ 8 - 62
modules/parent-join/src/main/java/org/elasticsearch/join/mapper/ParentIdFieldMapper.java

@@ -23,12 +23,6 @@ import org.apache.lucene.document.Field;
 import org.apache.lucene.document.FieldType;
 import org.apache.lucene.document.SortedDocValuesField;
 import org.apache.lucene.index.IndexOptions;
-import org.apache.lucene.index.Term;
-import org.apache.lucene.search.BooleanClause;
-import org.apache.lucene.search.BooleanQuery;
-import org.apache.lucene.search.ConstantScoreQuery;
-import org.apache.lucene.search.Query;
-import org.apache.lucene.search.TermQuery;
 import org.apache.lucene.util.BytesRef;
 import org.elasticsearch.common.lucene.Lucene;
 import org.elasticsearch.index.fielddata.IndexFieldData;
@@ -43,11 +37,8 @@ import org.elasticsearch.index.mapper.ValueFetcher;
 import org.elasticsearch.search.aggregations.support.CoreValuesSourceType;
 import org.elasticsearch.search.lookup.SearchLookup;
 
-import java.io.IOException;
-import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
-import java.util.Map;
-import java.util.Set;
 import java.util.function.Supplier;
 
 /**
@@ -69,17 +60,9 @@ public final class ParentIdFieldMapper extends FieldMapper {
     }
 
     static class Builder extends FieldMapper.Builder {
-        private final String parent;
-        private final Set<String> children;
 
-        Builder(String name, String parent, Set<String> children) {
+        Builder(String name) {
             super(name, Defaults.FIELD_TYPE);
-            this.parent = parent;
-            this.children = children;
-        }
-
-        public Set<String> getChildren() {
-            return children;
         }
 
         public Builder eagerGlobalOrdinals(boolean eagerGlobalOrdinals) {
@@ -89,14 +72,14 @@ public final class ParentIdFieldMapper extends FieldMapper {
 
         @Override
         public ParentIdFieldMapper build(BuilderContext context) {
-            return new ParentIdFieldMapper(name, parent, children, fieldType,
-                new ParentIdFieldType(buildFullName(context), eagerGlobalOrdinals, meta));
+            return new ParentIdFieldMapper(name, fieldType,
+                new ParentIdFieldType(buildFullName(context), eagerGlobalOrdinals));
         }
     }
 
     public static final class ParentIdFieldType extends StringFieldType {
-        private ParentIdFieldType(String name, boolean eagerGlobalOrdinals, Map<String, String> meta) {
-            super(name, true, false, true, TextSearchInfo.SIMPLE_MATCH_ONLY, meta);
+        public ParentIdFieldType(String name, boolean eagerGlobalOrdinals) {
+            super(name, true, false, true, TextSearchInfo.SIMPLE_MATCH_ONLY, Collections.emptyMap());
             setIndexAnalyzer(Lucene.KEYWORD_ANALYZER);
             setEagerGlobalOrdinals(eagerGlobalOrdinals);
         }
@@ -127,17 +110,10 @@ public final class ParentIdFieldMapper extends FieldMapper {
         }
     }
 
-    private final String parentName;
-    private Set<String> children;
-
     protected ParentIdFieldMapper(String simpleName,
-                                  String parentName,
-                                  Set<String> children,
                                   FieldType fieldType,
                                   MappedFieldType mappedFieldType) {
         super(simpleName, fieldType, mappedFieldType, MultiFields.empty(), CopyTo.empty());
-        this.parentName = parentName;
-        this.children = children;
     }
 
     @Override
@@ -145,37 +121,8 @@ public final class ParentIdFieldMapper extends FieldMapper {
         return (ParentIdFieldMapper) super.clone();
     }
 
-    /**
-     * Returns the parent name associated with this mapper.
-     */
-    public String getParentName() {
-        return parentName;
-    }
-
-    public Query getParentFilter() {
-        return new TermQuery(new Term(name().substring(0, name().indexOf('#')), parentName));
-    }
-    /**
-     * Returns the children names associated with this mapper.
-     */
-    public Collection<String> getChildren() {
-        return children;
-    }
-
-    public Query getChildFilter(String type) {
-        return new TermQuery(new Term(name().substring(0, name().indexOf('#')), type));
-    }
-
-    public Query getChildrenFilter() {
-        BooleanQuery.Builder builder = new BooleanQuery.Builder();
-        for (String child : children) {
-            builder.add(getChildFilter(child), BooleanClause.Occur.SHOULD);
-        }
-        return new ConstantScoreQuery(builder.build());
-    }
-
     @Override
-    protected void parseCreateField(ParseContext context) throws IOException {
+    protected void parseCreateField(ParseContext context) {
         if (context.externalValueSet() == false) {
             throw new IllegalStateException("external value not set");
         }
@@ -188,8 +135,7 @@ public final class ParentIdFieldMapper extends FieldMapper {
 
     @Override
     protected void mergeOptions(FieldMapper other, List<String> conflicts) {
-        ParentIdFieldMapper parentMergeWith = (ParentIdFieldMapper) other;
-        this.children = parentMergeWith.children;
+
     }
 
     @Override

+ 45 - 136
modules/parent-join/src/main/java/org/elasticsearch/join/mapper/ParentJoinFieldMapper.java

@@ -42,7 +42,6 @@ import org.elasticsearch.index.mapper.SourceValueFetcher;
 import org.elasticsearch.index.mapper.StringFieldType;
 import org.elasticsearch.index.mapper.TextSearchInfo;
 import org.elasticsearch.index.mapper.ValueFetcher;
-import org.elasticsearch.index.query.QueryShardContext;
 import org.elasticsearch.search.aggregations.support.CoreValuesSourceType;
 import org.elasticsearch.search.lookup.SearchLookup;
 
@@ -50,12 +49,12 @@ import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import java.util.function.Function;
 import java.util.function.Supplier;
 
 /**
@@ -80,34 +79,6 @@ public final class ParentJoinFieldMapper extends FieldMapper {
         }
     }
 
-    /**
-     * Returns the {@link ParentJoinFieldMapper} associated with the {@code context} or null
-     * if there is no parent-join field in this mapping.
-     */
-    public static ParentJoinFieldMapper getMapper(QueryShardContext context) {
-        return getMapper(context::getFieldType, context.getMapperService().documentMapper().mappers()::getMapper);
-    }
-
-    /**
-     * Returns the {@link ParentJoinFieldMapper} associated with the <code>service</code> or null
-     * if there is no parent-join field in this mapping.
-     */
-    public static ParentJoinFieldMapper getMapper(
-        Function<String, MappedFieldType> typeLookup,
-        Function<String, Mapper> mapperLookup
-    ) {
-        MetaJoinFieldMapper.MetaJoinFieldType fieldType =
-            (MetaJoinFieldMapper.MetaJoinFieldType) typeLookup.apply(MetaJoinFieldMapper.NAME);
-        if (fieldType == null) {
-            return null;
-        }
-        return (ParentJoinFieldMapper) mapperLookup.apply(fieldType.getJoinField());
-    }
-
-    private static String getParentIdFieldName(String joinFieldName, String parentName) {
-        return joinFieldName + "#" + parentName;
-    }
-
     private static void checkIndexCompatibility(IndexSettings settings, String name) {
         if (settings.getIndexMetadata().isRoutingPartitionedIndex()) {
             throw new IllegalStateException("cannot create join field [" + name + "] " +
@@ -122,32 +93,16 @@ public final class ParentJoinFieldMapper extends FieldMapper {
         }
     }
 
-    private static void checkParentFields(String name, List<ParentIdFieldMapper> mappers) {
-        Set<String> children = new HashSet<>();
-        List<String> conflicts = new ArrayList<>();
-        for (ParentIdFieldMapper mapper : mappers) {
-            for (String child : mapper.getChildren()) {
-                if (children.add(child) == false) {
-                    conflicts.add("[" + child + "] cannot have multiple parents");
-                }
-            }
-        }
-        if (conflicts.isEmpty() == false) {
-            throw new IllegalArgumentException("invalid definition for join field [" + name + "]:\n" + conflicts.toString());
-        }
-    }
-
     public static class Builder extends FieldMapper.Builder {
-        final List<ParentIdFieldMapper.Builder> parentIdFieldBuilders = new ArrayList<>();
+        final List<Relations> relations = new ArrayList<>();
         boolean eagerGlobalOrdinals = true;
 
         public Builder(String name) {
             super(name, Defaults.FIELD_TYPE);
         }
 
-        public Builder addParent(String parent, Set<String> children) {
-            String parentIdFieldName = getParentIdFieldName(name, parent);
-            parentIdFieldBuilders.add(new ParentIdFieldMapper.Builder(parentIdFieldName, parent, children));
+        public Builder addRelation(String parent, Set<String> children) {
+            relations.add(new Relations(parent, children));
             return this;
         }
 
@@ -159,19 +114,20 @@ public final class ParentJoinFieldMapper extends FieldMapper {
         @Override
         public ParentJoinFieldMapper build(BuilderContext context) {
             checkObjectOrNested(context.path(), name);
-            final List<ParentIdFieldMapper> parentIdFields = new ArrayList<>();
-            parentIdFieldBuilders.stream()
-                .map((parentBuilder) -> {
+            final Map<String, ParentIdFieldMapper> parentIdFields = new HashMap<>();
+            relations.stream()
+                .map(relation -> {
+                    ParentIdFieldMapper.Builder builder = new ParentIdFieldMapper.Builder(name() + "#" + relation.parent);
                     if (eagerGlobalOrdinals) {
-                        parentBuilder.eagerGlobalOrdinals(true);
+                        builder.eagerGlobalOrdinals(true);
                     }
-                    return parentBuilder.build(context);
+                    return builder.build(context);
                 })
-                .forEach(parentIdFields::add);
-            checkParentFields(name(), parentIdFields);
+                .forEach(mapper -> parentIdFields.put(mapper.name(), mapper));
             MetaJoinFieldMapper unique = new MetaJoinFieldMapper.Builder(name).build(context);
-            return new ParentJoinFieldMapper(name, fieldType, new JoinFieldType(buildFullName(context), meta),
-                unique, Collections.unmodifiableList(parentIdFields), eagerGlobalOrdinals);
+            Joiner joiner = new Joiner(name(), relations);
+            return new ParentJoinFieldMapper(name, fieldType, new JoinFieldType(buildFullName(context), joiner, meta),
+                unique, Collections.unmodifiableMap(parentIdFields), eagerGlobalOrdinals, relations);
         }
     }
 
@@ -203,7 +159,7 @@ public final class ParentJoinFieldMapper extends FieldMapper {
                         } else {
                             children = Collections.singleton(relation.getValue().toString());
                         }
-                        builder.addParent(parent, children);
+                        builder.addRelation(parent, children);
                     }
                     iterator.remove();
                 }
@@ -213,9 +169,17 @@ public final class ParentJoinFieldMapper extends FieldMapper {
     }
 
     public static final class JoinFieldType extends StringFieldType {
-        private JoinFieldType(String name, Map<String, String> meta) {
+
+        private final Joiner joiner;
+
+        private JoinFieldType(String name, Joiner joiner, Map<String, String> meta) {
             super(name, true, false, true, TextSearchInfo.SIMPLE_MATCH_ONLY, meta);
             setIndexAnalyzer(Lucene.KEYWORD_ANALYZER);
+            this.joiner = joiner;
+        }
+
+        Joiner getJoiner() {
+            return joiner;
         }
 
         @Override
@@ -246,19 +210,21 @@ public final class ParentJoinFieldMapper extends FieldMapper {
 
     // The meta field that ensures that there is no other parent-join in the mapping
     private MetaJoinFieldMapper uniqueFieldMapper;
-    private List<ParentIdFieldMapper> parentIdFields;
+    private Map<String, ParentIdFieldMapper> parentIdFields;
+    private List<Relations> relations;
     private boolean eagerGlobalOrdinals;
 
     protected ParentJoinFieldMapper(String simpleName,
                                     FieldType fieldType,
                                     MappedFieldType mappedFieldType,
                                     MetaJoinFieldMapper uniqueFieldMapper,
-                                    List<ParentIdFieldMapper> parentIdFields,
-                                    boolean eagerGlobalOrdinals) {
+                                    Map<String, ParentIdFieldMapper> parentIdFields,
+                                    boolean eagerGlobalOrdinals, List<Relations> relations) {
         super(simpleName, fieldType, mappedFieldType, MultiFields.empty(), CopyTo.empty());
         this.parentIdFields = parentIdFields;
         this.uniqueFieldMapper = uniqueFieldMapper;
         this.eagerGlobalOrdinals = eagerGlobalOrdinals;
+        this.relations = relations;
     }
 
     @Override
@@ -278,76 +244,21 @@ public final class ParentJoinFieldMapper extends FieldMapper {
 
     @Override
     public Iterator<Mapper> iterator() {
-        List<Mapper> mappers = new ArrayList<> (parentIdFields);
+        List<Mapper> mappers = new ArrayList<>(parentIdFields.values());
         mappers.add(uniqueFieldMapper);
         return mappers.iterator();
     }
 
-    /**
-     * Returns true if <code>name</code> is a parent name in the field.
-     */
-    public boolean hasParent(String name) {
-        return parentIdFields.stream().anyMatch((mapper) -> name.equals(mapper.getParentName()));
-    }
-
-    /**
-     * Returns true if <code>name</code> is a child name in the field.
-     */
-    public boolean hasChild(String name) {
-        return parentIdFields.stream().anyMatch((mapper) -> mapper.getChildren().contains(name));
-    }
-
-    /**
-     * Returns the parent Id field mapper associated with a parent <code>name</code>
-     * if <code>isParent</code> is true and a child <code>name</code> otherwise.
-     */
-    public ParentIdFieldMapper getParentIdFieldMapper(String name, boolean isParent) {
-        for (ParentIdFieldMapper mapper : parentIdFields) {
-            if (isParent && name.equals(mapper.getParentName())) {
-                return mapper;
-            } else if (isParent == false && mapper.getChildren().contains(name)) {
-                return mapper;
-            }
-        }
-        return null;
-    }
-
     @Override
     protected void mergeOptions(FieldMapper other, List<String> conflicts) {
         ParentJoinFieldMapper joinMergeWith = (ParentJoinFieldMapper) other;
-        final List<ParentIdFieldMapper> newParentIdFields = new ArrayList<>();
-        for (ParentIdFieldMapper mapper : parentIdFields) {
-            if (joinMergeWith.getParentIdFieldMapper(mapper.getParentName(), true) == null) {
-                conflicts.add("cannot remove parent [" + mapper.getParentName() + "] in join field [" + name() + "]");
-            }
-        }
-        for (ParentIdFieldMapper mergeWithMapper : joinMergeWith.parentIdFields) {
-            ParentIdFieldMapper self = getParentIdFieldMapper(mergeWithMapper.getParentName(), true);
-            if (self == null) {
-                if (getParentIdFieldMapper(mergeWithMapper.getParentName(), false) != null) {
-                    // it is forbidden to add a parent to an existing child
-                    conflicts.add("cannot create parent [" + mergeWithMapper.getParentName()  + "] from an existing child");
-                }
-                for (String child : mergeWithMapper.getChildren()) {
-                    if (getParentIdFieldMapper(child, true) != null) {
-                        // it is forbidden to add a parent to an existing child
-                        conflicts.add("cannot create child [" + child  + "] from an existing parent");
-                    }
-                }
-                newParentIdFields.add(mergeWithMapper);
-            } else {
-                for (String child : self.getChildren()) {
-                    if (mergeWithMapper.getChildren().contains(child) == false) {
-                        conflicts.add("cannot remove child [" + child + "] in join field [" + name() + "]");
-                    }
-                }
-                ParentIdFieldMapper merged = (ParentIdFieldMapper) self.merge(mergeWithMapper);
-                newParentIdFields.add(merged);
-            }
+        if (fieldType().joiner.canMerge(joinMergeWith.fieldType().joiner, conflicts::add) == false) {
+            return;
         }
         this.eagerGlobalOrdinals = joinMergeWith.eagerGlobalOrdinals;
-        this.parentIdFields = Collections.unmodifiableList(newParentIdFields);
+        this.parentIdFields = joinMergeWith.parentIdFields;
         this.uniqueFieldMapper = (MetaJoinFieldMapper) uniqueFieldMapper.merge(joinMergeWith.uniqueFieldMapper);
+        this.relations = joinMergeWith.relations;
     }
 
     @Override
@@ -389,12 +300,10 @@ public final class ParentJoinFieldMapper extends FieldMapper {
             throw new IllegalStateException("[" + name  + "] expected START_OBJECT or VALUE_STRING but was: " + token);
         }
 
-        ParentIdFieldMapper parentIdField = getParentIdFieldMapper(name, true);
-        ParentIdFieldMapper childParentIdField = getParentIdFieldMapper(name, false);
-        if (parentIdField == null && childParentIdField == null) {
+        if (fieldType().joiner.knownRelation(name) == false) {
             throw new IllegalArgumentException("unknown join name [" + name + "] for field [" + name() + "]");
         }
-        if (childParentIdField != null) {
+        if (fieldType().joiner.childTypeExists(name)) {
             // Index the document as a child
             if (parent == null) {
                 throw new IllegalArgumentException("[parent] is missing for join field [" + name() + "]");
@@ -402,15 +311,15 @@ public final class ParentJoinFieldMapper extends FieldMapper {
             if (context.sourceToParse().routing() == null) {
                 throw new IllegalArgumentException("[routing] is missing for join field [" + name() + "]");
             }
-            assert childParentIdField.getChildren().contains(name);
             ParseContext externalContext = context.createExternalValueContext(parent);
-            childParentIdField.parse(externalContext);
+            String fieldName = fieldType().joiner.parentJoinField(name);
+            parentIdFields.get(fieldName).parse(externalContext);
         }
-        if (parentIdField != null) {
+        if (fieldType().joiner.parentTypeExists(name)) {
             // Index the document as a parent
-            assert parentIdField.getParentName().equals(name);
             ParseContext externalContext = context.createExternalValueContext(context.sourceToParse().id());
-            parentIdField.parse(externalContext);
+            String fieldName = fieldType().joiner.childJoinField(name);
+            parentIdFields.get(fieldName).parse(externalContext);
         }
 
         BytesRef binaryValue = new BytesRef(name);
@@ -425,11 +334,11 @@ public final class ParentJoinFieldMapper extends FieldMapper {
         builder.field("type", contentType());
         builder.field("eager_global_ordinals", eagerGlobalOrdinals);
         builder.startObject("relations");
-        for (ParentIdFieldMapper field : parentIdFields) {
-            if (field.getChildren().size() == 1) {
-                builder.field(field.getParentName(), field.getChildren().iterator().next());
+        for (Relations relation : relations) {
+            if (relation.children.size() == 1) {
+                builder.field(relation.parent, relation.children.iterator().next());
             } else {
-                builder.field(field.getParentName(), field.getChildren());
+                builder.field(relation.parent, relation.children);
             }
         }
         builder.endObject();

+ 56 - 0
modules/parent-join/src/main/java/org/elasticsearch/join/mapper/Relations.java

@@ -0,0 +1,56 @@
+/*
+ * 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.mapper;
+
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * Defines a relationship between a parent type and a set of child types
+ */
+class Relations {
+
+    final String parent;
+    final Set<String> children;
+
+    Relations(String parent, Set<String> children) {
+        this.parent = parent;
+        this.children = children;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        Relations relation = (Relations) o;
+        return Objects.equals(parent, relation.parent) &&
+            Objects.equals(children, relation.children);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(parent, children);
+    }
+
+    @Override
+    public String toString() {
+        return parent + "->" + children;
+    }
+}

+ 22 - 16
modules/parent-join/src/main/java/org/elasticsearch/join/query/HasChildQueryBuilder.java

@@ -46,8 +46,7 @@ import org.elasticsearch.index.query.QueryBuilder;
 import org.elasticsearch.index.query.QueryRewriteContext;
 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 org.elasticsearch.join.mapper.Joiner;
 
 import java.io.IOException;
 import java.util.HashMap;
@@ -303,8 +302,8 @@ public class HasChildQueryBuilder extends AbstractQueryBuilder<HasChildQueryBuil
                     ALLOW_EXPENSIVE_QUERIES.getKey() + "' is set to false.");
         }
 
-        ParentJoinFieldMapper joinFieldMapper = ParentJoinFieldMapper.getMapper(context);
-        if (joinFieldMapper == null) {
+        Joiner joiner = Joiner.getJoiner(context);
+        if (joiner == null) {
             if (ignoreUnmapped) {
                 return new MatchNoDocsQuery();
             } else {
@@ -312,30 +311,37 @@ public class HasChildQueryBuilder extends AbstractQueryBuilder<HasChildQueryBuil
             }
         }
 
-        ParentIdFieldMapper parentIdFieldMapper = joinFieldMapper.getParentIdFieldMapper(type, false);
-        if (parentIdFieldMapper != null) {
-            Query parentFilter = parentIdFieldMapper.getParentFilter();
-            Query childFilter = parentIdFieldMapper.getChildFilter(type);
-            Query innerQuery = Queries.filtered(query.toQuery(context), childFilter);
-            MappedFieldType fieldType = parentIdFieldMapper.fieldType();
-            final SortedSetOrdinalsIndexFieldData fieldData = context.getForField(fieldType);
-            return new LateParsingQuery(parentFilter, innerQuery, minChildren(), maxChildren(),
-                fieldType.name(), scoreMode, fieldData, context.getSearchSimilarity());
-        } else {
+        if (joiner.childTypeExists(type) == false) {
             if (ignoreUnmapped) {
                 return new MatchNoDocsQuery();
             } else {
-                throw new QueryShardException(context, "[" + NAME + "] join field [" + joinFieldMapper.name() +
+                throw new QueryShardException(context, "[" + NAME + "] join field [" + joiner.getJoinField() +
                     "] doesn't hold [" + type + "] as a child");
             }
         }
+
+        String parentJoinField = joiner.parentJoinField(type);
+        if (context.isFieldMapped(parentJoinField) == false) {
+            if (ignoreUnmapped) {
+                return new MatchNoDocsQuery();
+            }
+            throw new QueryShardException(context, "[" + NAME + "] no parent join field [" + parentJoinField + "] configured");
+        }
+
+        Query parentFilter = joiner.parentFilter(type);
+        Query childFilter = joiner.filter(type);
+        Query filteredQuery = Queries.filtered(query.toQuery(context), childFilter);
+        MappedFieldType ft = context.getFieldType(parentJoinField);
+        final SortedSetOrdinalsIndexFieldData fieldData = context.getForField(ft);
+        return new LateParsingQuery(parentFilter, filteredQuery, minChildren, maxChildren,
+            parentJoinField, scoreMode, fieldData, context.getSearchSimilarity());
     }
 
     /**
      * A query that rewrites into another query using
      * {@link JoinUtil#createJoinQuery(String, Query, Query, IndexSearcher, ScoreMode, OrdinalMap, int, int)}
      * that executes the actual join.
-     *
+     * <p>
      * This query is exclusively used by the {@link HasChildQueryBuilder} and {@link HasParentQueryBuilder} to get access
      * to the {@link DirectoryReader} used by the current search in order to retrieve the {@link OrdinalMap}.
      * The {@link OrdinalMap} is required by {@link JoinUtil} to execute the join.

+ 26 - 29
modules/parent-join/src/main/java/org/elasticsearch/join/query/HasParentQueryBuilder.java

@@ -38,8 +38,7 @@ import org.elasticsearch.index.query.QueryBuilder;
 import org.elasticsearch.index.query.QueryRewriteContext;
 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 org.elasticsearch.join.mapper.Joiner;
 
 import java.io.IOException;
 import java.util.HashMap;
@@ -66,7 +65,7 @@ public class HasParentQueryBuilder extends AbstractQueryBuilder<HasParentQueryBu
     private static final ParseField IGNORE_UNMAPPED_FIELD = new ParseField("ignore_unmapped");
 
     private final QueryBuilder query;
-    private final String type;
+    private final String parentType;
     private final boolean score;
     private InnerHitBuilder innerHitBuilder;
     private boolean ignoreUnmapped = false;
@@ -76,7 +75,7 @@ public class HasParentQueryBuilder extends AbstractQueryBuilder<HasParentQueryBu
     }
 
     private HasParentQueryBuilder(String type, QueryBuilder query, boolean score, InnerHitBuilder innerHitBuilder) {
-        this.type = requireValue(type, "[" + NAME + "] requires '" + PARENT_TYPE_FIELD.getPreferredName()  + "' field");
+        this.parentType = requireValue(type, "[" + NAME + "] requires '" + PARENT_TYPE_FIELD.getPreferredName()  + "' field");
         this.query = requireValue(query, "[" + NAME + "] requires '" + QUERY_FIELD.getPreferredName() + "' field");
         this.score = score;
         this.innerHitBuilder = innerHitBuilder;
@@ -87,7 +86,7 @@ public class HasParentQueryBuilder extends AbstractQueryBuilder<HasParentQueryBu
      */
     public HasParentQueryBuilder(StreamInput in) throws IOException {
         super(in);
-        type = in.readString();
+        parentType = in.readString();
         score = in.readBoolean();
         query = in.readNamedWriteable(QueryBuilder.class);
         innerHitBuilder = in.readOptionalWriteable(InnerHitBuilder::new);
@@ -96,7 +95,7 @@ public class HasParentQueryBuilder extends AbstractQueryBuilder<HasParentQueryBu
 
     @Override
     protected void doWriteTo(StreamOutput out) throws IOException {
-        out.writeString(type);
+        out.writeString(parentType);
         out.writeBoolean(score);
         out.writeNamedWriteable(query);
         out.writeOptionalWriteable(innerHitBuilder);
@@ -121,7 +120,7 @@ public class HasParentQueryBuilder extends AbstractQueryBuilder<HasParentQueryBu
      * Returns the parents type name
      */
     public String type() {
-        return type;
+        return parentType;
     }
 
     /**
@@ -166,33 +165,31 @@ public class HasParentQueryBuilder extends AbstractQueryBuilder<HasParentQueryBu
                     ALLOW_EXPENSIVE_QUERIES.getKey() + "' is set to false.");
         }
 
-        ParentJoinFieldMapper joinFieldMapper = ParentJoinFieldMapper.getMapper(context);
-        if (joinFieldMapper == null) {
+        Joiner joiner = Joiner.getJoiner(context);
+        if (joiner == null) {
             if (ignoreUnmapped) {
                 return new MatchNoDocsQuery();
             } else {
                 throw new QueryShardException(context, "[" + NAME + "] no join field has been configured");
             }
         }
-
-        ParentIdFieldMapper parentIdFieldMapper = joinFieldMapper.getParentIdFieldMapper(type, true);
-        if (parentIdFieldMapper != null) {
-            Query parentFilter = parentIdFieldMapper.getParentFilter();
-            Query innerQuery = Queries.filtered(query.toQuery(context), parentFilter);
-            Query childFilter = parentIdFieldMapper.getChildrenFilter();
-            MappedFieldType fieldType = parentIdFieldMapper.fieldType();
-            final SortedSetOrdinalsIndexFieldData fieldData = context.getForField(fieldType);
-            return new HasChildQueryBuilder.LateParsingQuery(childFilter, innerQuery,
-                HasChildQueryBuilder.DEFAULT_MIN_CHILDREN, HasChildQueryBuilder.DEFAULT_MAX_CHILDREN,
-                fieldType.name(), score ? ScoreMode.Max : ScoreMode.None, fieldData, context.getSearchSimilarity());
-        } else {
+        if (joiner.parentTypeExists(parentType) == false) {
             if (ignoreUnmapped) {
                 return new MatchNoDocsQuery();
             } else {
-                throw new QueryShardException(context, "[" + NAME + "] join field [" + joinFieldMapper.name() +
-                    "] doesn't hold [" + type + "] as a parent");
+                throw new QueryShardException(context, "[" + NAME + "] join field [" + joiner.getJoinField() +
+                    "] doesn't hold [" + parentType + "] as a parent");
             }
         }
+
+        Query parentFilter = joiner.filter(parentType);
+        Query innerQuery = Queries.filtered(query.toQuery(context), parentFilter);
+        Query childFilter = joiner.childrenFilter(parentType);
+        MappedFieldType fieldType = context.getFieldType(joiner.childJoinField(parentType));
+        final SortedSetOrdinalsIndexFieldData fieldData = context.getForField(fieldType);
+        return new HasChildQueryBuilder.LateParsingQuery(childFilter, innerQuery,
+            HasChildQueryBuilder.DEFAULT_MIN_CHILDREN, HasChildQueryBuilder.DEFAULT_MAX_CHILDREN,
+            fieldType.name(), score ? ScoreMode.Max : ScoreMode.None, fieldData, context.getSearchSimilarity());
     }
 
     @Override
@@ -200,7 +197,7 @@ public class HasParentQueryBuilder extends AbstractQueryBuilder<HasParentQueryBu
         builder.startObject(NAME);
         builder.field(QUERY_FIELD.getPreferredName());
         query.toXContent(builder, params);
-        builder.field(PARENT_TYPE_FIELD.getPreferredName(), type);
+        builder.field(PARENT_TYPE_FIELD.getPreferredName(), parentType);
         builder.field(SCORE_FIELD.getPreferredName(), score);
         builder.field(IGNORE_UNMAPPED_FIELD.getPreferredName(), ignoreUnmapped);
         printBoostAndQueryName(builder);
@@ -268,7 +265,7 @@ public class HasParentQueryBuilder extends AbstractQueryBuilder<HasParentQueryBu
     @Override
     protected boolean doEquals(HasParentQueryBuilder that) {
         return Objects.equals(query, that.query)
-                && Objects.equals(type, that.type)
+                && Objects.equals(parentType, that.parentType)
                 && Objects.equals(score, that.score)
                 && Objects.equals(innerHitBuilder, that.innerHitBuilder)
                 && Objects.equals(ignoreUnmapped, that.ignoreUnmapped);
@@ -276,14 +273,14 @@ public class HasParentQueryBuilder extends AbstractQueryBuilder<HasParentQueryBu
 
     @Override
     protected int doHashCode() {
-        return Objects.hash(query, type, score, innerHitBuilder, ignoreUnmapped);
+        return Objects.hash(query, parentType, score, innerHitBuilder, ignoreUnmapped);
     }
 
     @Override
     protected QueryBuilder doRewrite(QueryRewriteContext queryShardContext) throws IOException {
         QueryBuilder rewrittenQuery = query.rewrite(queryShardContext);
         if (rewrittenQuery != query) {
-            HasParentQueryBuilder hasParentQueryBuilder = new HasParentQueryBuilder(type, rewrittenQuery, score, innerHitBuilder);
+            HasParentQueryBuilder hasParentQueryBuilder = new HasParentQueryBuilder(parentType, rewrittenQuery, score, innerHitBuilder);
             hasParentQueryBuilder.ignoreUnmapped(ignoreUnmapped);
             return hasParentQueryBuilder;
         }
@@ -293,7 +290,7 @@ public class HasParentQueryBuilder extends AbstractQueryBuilder<HasParentQueryBu
     @Override
     protected void extractInnerHitBuilders(Map<String, InnerHitContextBuilder> innerHits) {
         if (innerHitBuilder != null) {
-            String name = innerHitBuilder.getName() != null ? innerHitBuilder.getName() : type;
+            String name = innerHitBuilder.getName() != null ? innerHitBuilder.getName() : parentType;
             if (innerHits.containsKey(name)) {
                 throw new IllegalArgumentException("[inner_hits] already contains an entry for key [" + name + "]");
             }
@@ -301,7 +298,7 @@ public class HasParentQueryBuilder extends AbstractQueryBuilder<HasParentQueryBu
             Map<String, InnerHitContextBuilder> children = new HashMap<>();
             InnerHitContextBuilder.extractInnerHits(query, children);
             InnerHitContextBuilder innerHitContextBuilder =
-                new ParentChildInnerHitContextBuilder(type, false, query, innerHitBuilder, children);
+                new ParentChildInnerHitContextBuilder(parentType, false, query, innerHitBuilder, children);
             innerHits.put(name, innerHitContextBuilder);
         }
     }

+ 13 - 18
modules/parent-join/src/main/java/org/elasticsearch/join/query/ParentChildInnerHitContextBuilder.java

@@ -21,11 +21,13 @@ package org.elasticsearch.join.query;
 import org.apache.lucene.index.LeafReaderContext;
 import org.apache.lucene.index.ReaderUtil;
 import org.apache.lucene.index.SortedDocValues;
+import org.apache.lucene.index.Term;
 import org.apache.lucene.search.BooleanClause;
 import org.apache.lucene.search.BooleanQuery;
 import org.apache.lucene.search.MultiCollector;
 import org.apache.lucene.search.Query;
 import org.apache.lucene.search.ScoreMode;
+import org.apache.lucene.search.TermQuery;
 import org.apache.lucene.search.TopDocs;
 import org.apache.lucene.search.TopDocsCollector;
 import org.apache.lucene.search.TopFieldCollector;
@@ -43,8 +45,7 @@ import org.elasticsearch.index.query.InnerHitBuilder;
 import org.elasticsearch.index.query.InnerHitContextBuilder;
 import org.elasticsearch.index.query.QueryBuilder;
 import org.elasticsearch.index.query.QueryShardContext;
-import org.elasticsearch.join.mapper.ParentIdFieldMapper;
-import org.elasticsearch.join.mapper.ParentJoinFieldMapper;
+import org.elasticsearch.join.mapper.Joiner;
 import org.elasticsearch.search.SearchHit;
 import org.elasticsearch.search.fetch.subphase.InnerHitsContext;
 import org.elasticsearch.search.internal.SearchContext;
@@ -69,11 +70,11 @@ class ParentChildInnerHitContextBuilder extends InnerHitContextBuilder {
     @Override
     protected void doBuild(SearchContext context, InnerHitsContext innerHitsContext) throws IOException {
         QueryShardContext queryShardContext = context.getQueryShardContext();
-        ParentJoinFieldMapper joinFieldMapper = ParentJoinFieldMapper.getMapper(context.getQueryShardContext());
-        if (joinFieldMapper != null) {
+        Joiner joiner = Joiner.getJoiner(queryShardContext);
+        if (joiner != null) {
             String name = innerHitBuilder.getName() != null ? innerHitBuilder.getName() : typeName;
             JoinFieldInnerHitSubContext joinFieldInnerHits = new JoinFieldInnerHitSubContext(name, context, typeName,
-                fetchChildInnerHits, joinFieldMapper);
+                fetchChildInnerHits, joiner);
             setupInnerHitsContext(queryShardContext, joinFieldInnerHits);
             innerHitsContext.addInnerHitDefinition(joinFieldInnerHits);
         } else {
@@ -86,42 +87,36 @@ class ParentChildInnerHitContextBuilder extends InnerHitContextBuilder {
     static final class JoinFieldInnerHitSubContext extends InnerHitsContext.InnerHitSubContext {
         private final String typeName;
         private final boolean fetchChildInnerHits;
-        private final ParentJoinFieldMapper joinFieldMapper;
+        private final Joiner joiner;
 
         JoinFieldInnerHitSubContext(String name, SearchContext context, String typeName, boolean fetchChildInnerHits,
-                                    ParentJoinFieldMapper joinFieldMapper) {
+                                    Joiner joiner) {
             super(name, context);
             this.typeName = typeName;
             this.fetchChildInnerHits = fetchChildInnerHits;
-            this.joinFieldMapper = joinFieldMapper;
+            this.joiner = joiner;
         }
 
         @Override
         public TopDocsAndMaxScore topDocs(SearchHit hit) throws IOException {
             Weight innerHitQueryWeight = getInnerHitQueryWeight();
-            String joinName = getSortedDocValue(joinFieldMapper.name(), context, hit.docId());
+            String joinName = getSortedDocValue(joiner.getJoinField(), context, hit.docId());
             if (joinName == null) {
                 return new TopDocsAndMaxScore(Lucene.EMPTY_TOP_DOCS, Float.NaN);
             }
 
             QueryShardContext qsc = context.getQueryShardContext();
-            ParentIdFieldMapper parentIdFieldMapper =
-                joinFieldMapper.getParentIdFieldMapper(typeName, fetchChildInnerHits == false);
-            if (parentIdFieldMapper == null) {
-                return new TopDocsAndMaxScore(Lucene.EMPTY_TOP_DOCS, Float.NaN);
-            }
-
             Query q;
             if (fetchChildInnerHits) {
-                Query hitQuery = parentIdFieldMapper.fieldType().termQuery(hit.getId(), qsc);
+                Query hitQuery = new TermQuery(new Term(joiner.parentJoinField(typeName), hit.getId()));
                 q = new BooleanQuery.Builder()
                     // Only include child documents that have the current hit as parent:
                     .add(hitQuery, BooleanClause.Occur.FILTER)
                     // and only include child documents of a single relation:
-                    .add(joinFieldMapper.fieldType().termQuery(typeName, qsc), BooleanClause.Occur.FILTER)
+                    .add(new TermQuery(new Term(joiner.getJoinField(), typeName)), BooleanClause.Occur.FILTER)
                     .build();
             } else {
-                String parentId = getSortedDocValue(parentIdFieldMapper.name(), context, hit.docId());
+                String parentId = getSortedDocValue(joiner.childJoinField(typeName), context, hit.docId());
                 if (parentId == null) {
                     return new TopDocsAndMaxScore(Lucene.EMPTY_TOP_DOCS, Float.NaN);
                 }

+ 8 - 8
modules/parent-join/src/main/java/org/elasticsearch/join/query/ParentIdQueryBuilder.java

@@ -19,10 +19,12 @@
 
 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.MatchNoDocsQuery;
 import org.apache.lucene.search.Query;
+import org.apache.lucene.search.TermQuery;
 import org.elasticsearch.ElasticsearchException;
 import org.elasticsearch.common.ParseField;
 import org.elasticsearch.common.ParsingException;
@@ -33,8 +35,7 @@ import org.elasticsearch.common.xcontent.XContentParser;
 import org.elasticsearch.index.query.AbstractQueryBuilder;
 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 org.elasticsearch.join.mapper.Joiner;
 
 import java.io.IOException;
 import java.util.Objects;
@@ -161,8 +162,8 @@ public final class ParentIdQueryBuilder extends AbstractQueryBuilder<ParentIdQue
                     ALLOW_EXPENSIVE_QUERIES.getKey() + "' is set to false.");
         }
 
-        ParentJoinFieldMapper joinFieldMapper = ParentJoinFieldMapper.getMapper(context);
-        if (joinFieldMapper == null) {
+        Joiner joiner = Joiner.getJoiner(context);
+        if (joiner == null) {
             if (ignoreUnmapped) {
                 return new MatchNoDocsQuery();
             } else {
@@ -170,8 +171,7 @@ public final class ParentIdQueryBuilder extends AbstractQueryBuilder<ParentIdQue
                 throw new QueryShardException(context, "[" + NAME + "] no join field found for index [" + indexName  + "]");
             }
         }
-        final ParentIdFieldMapper childMapper = joinFieldMapper.getParentIdFieldMapper(type, false);
-        if (childMapper == null) {
+        if (joiner.childTypeExists(type) == false) {
             if (ignoreUnmapped) {
                 return new MatchNoDocsQuery();
             } else {
@@ -179,9 +179,9 @@ public final class ParentIdQueryBuilder extends AbstractQueryBuilder<ParentIdQue
             }
         }
         return new BooleanQuery.Builder()
-            .add(childMapper.fieldType().termQuery(id, context), BooleanClause.Occur.MUST)
+            .add(new TermQuery(new Term(joiner.parentJoinField(type), 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(joinFieldMapper.fieldType().termQuery(type, context), BooleanClause.Occur.FILTER)
+            .add(new TermQuery(new Term(joiner.getJoinField(), type)), BooleanClause.Occur.FILTER)
             .build();
     }
 

+ 5 - 8
modules/parent-join/src/test/java/org/elasticsearch/join/aggregations/ChildrenToParentAggregatorTests.java

@@ -39,17 +39,16 @@ import org.elasticsearch.common.lucene.index.ElasticsearchDirectoryReader;
 import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.index.Index;
 import org.elasticsearch.index.mapper.ContentPath;
-import org.elasticsearch.index.mapper.DocumentMapper;
 import org.elasticsearch.index.mapper.IdFieldMapper;
 import org.elasticsearch.index.mapper.MappedFieldType;
 import org.elasticsearch.index.mapper.Mapper;
 import org.elasticsearch.index.mapper.MapperService;
-import org.elasticsearch.index.mapper.MappingLookup;
 import org.elasticsearch.index.mapper.NumberFieldMapper;
 import org.elasticsearch.index.mapper.Uid;
 import org.elasticsearch.index.shard.ShardId;
 import org.elasticsearch.join.ParentJoinPlugin;
 import org.elasticsearch.join.mapper.MetaJoinFieldMapper;
+import org.elasticsearch.join.mapper.ParentIdFieldMapper;
 import org.elasticsearch.join.mapper.ParentJoinFieldMapper;
 import org.elasticsearch.plugins.SearchPlugin;
 import org.elasticsearch.search.aggregations.Aggregation;
@@ -281,18 +280,16 @@ public class ChildrenToParentAggregatorTests extends AggregatorTestCase {
         MetaJoinFieldMapper.MetaJoinFieldType metaJoinFieldType = mock(MetaJoinFieldMapper.MetaJoinFieldType.class);
         when(metaJoinFieldType.getJoinField()).thenReturn("join_field");
         when(mapperService.fieldType("_parent_join")).thenReturn(metaJoinFieldType);
-        MappingLookup fieldMappers = new MappingLookup(Collections.singleton(joinFieldMapper),
-            Collections.emptyList(), Collections.emptyList(), 0, null);
-        DocumentMapper mockMapper = mock(DocumentMapper.class);
-        when(mockMapper.mappers()).thenReturn(fieldMappers);
-        when(mapperService.documentMapper()).thenReturn(mockMapper);
+        when(mapperService.fieldType("join_field")).thenReturn(joinFieldMapper.fieldType());
+        when(mapperService.fieldType("join_field#" + PARENT_TYPE))
+            .thenReturn(new ParentIdFieldMapper.ParentIdFieldType("join_field#" + PARENT_TYPE, false));
         return mapperService;
     }
 
     private static ParentJoinFieldMapper createJoinFieldMapper() {
         Settings settings = Settings.builder().put(IndexMetadata.SETTING_VERSION_CREATED, Version.CURRENT).build();
         return new ParentJoinFieldMapper.Builder("join_field")
-                .addParent(PARENT_TYPE, Collections.singleton(CHILD_TYPE))
+                .addRelation(PARENT_TYPE, Collections.singleton(CHILD_TYPE))
                 .build(new Mapper.BuilderContext(settings, new ContentPath(0)));
     }
 

+ 5 - 8
modules/parent-join/src/test/java/org/elasticsearch/join/aggregations/ParentToChildrenAggregatorTests.java

@@ -40,8 +40,6 @@ import org.elasticsearch.common.lucene.index.ElasticsearchDirectoryReader;
 import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.index.Index;
 import org.elasticsearch.index.mapper.ContentPath;
-import org.elasticsearch.index.mapper.MappingLookup;
-import org.elasticsearch.index.mapper.DocumentMapper;
 import org.elasticsearch.index.mapper.IdFieldMapper;
 import org.elasticsearch.index.mapper.MappedFieldType;
 import org.elasticsearch.index.mapper.Mapper;
@@ -51,6 +49,7 @@ import org.elasticsearch.index.mapper.Uid;
 import org.elasticsearch.index.shard.ShardId;
 import org.elasticsearch.join.ParentJoinPlugin;
 import org.elasticsearch.join.mapper.MetaJoinFieldMapper;
+import org.elasticsearch.join.mapper.ParentIdFieldMapper;
 import org.elasticsearch.join.mapper.ParentJoinFieldMapper;
 import org.elasticsearch.plugins.SearchPlugin;
 import org.elasticsearch.search.aggregations.AggregationBuilder;
@@ -233,18 +232,16 @@ public class ParentToChildrenAggregatorTests extends AggregatorTestCase {
         MetaJoinFieldMapper.MetaJoinFieldType metaJoinFieldType = mock(MetaJoinFieldMapper.MetaJoinFieldType.class);
         when(metaJoinFieldType.getJoinField()).thenReturn("join_field");
         when(mapperService.fieldType("_parent_join")).thenReturn(metaJoinFieldType);
-        MappingLookup fieldMappers = new MappingLookup(Collections.singleton(joinFieldMapper),
-            Collections.emptyList(), Collections.emptyList(), 0, null);
-        DocumentMapper mockMapper = mock(DocumentMapper.class);
-        when(mockMapper.mappers()).thenReturn(fieldMappers);
-        when(mapperService.documentMapper()).thenReturn(mockMapper);
+        when(mapperService.fieldType("join_field")).thenReturn(joinFieldMapper.fieldType());
+        when(mapperService.fieldType("join_field#" + PARENT_TYPE))
+            .thenReturn(new ParentIdFieldMapper.ParentIdFieldType("join_field#" + PARENT_TYPE, false));
         return mapperService;
     }
 
     private static ParentJoinFieldMapper createJoinFieldMapper() {
         Settings settings = Settings.builder().put(IndexMetadata.SETTING_VERSION_CREATED, Version.CURRENT).build();
         return new ParentJoinFieldMapper.Builder("join_field")
-                .addParent(PARENT_TYPE, Collections.singleton(CHILD_TYPE))
+                .addRelation(PARENT_TYPE, Collections.singleton(CHILD_TYPE))
                 .build(new Mapper.BuilderContext(settings, new ContentPath(0)));
     }
 

+ 41 - 31
modules/parent-join/src/test/java/org/elasticsearch/join/mapper/ParentJoinFieldMapperTests.java

@@ -19,6 +19,8 @@
 
 package org.elasticsearch.join.mapper;
 
+import org.apache.lucene.index.Term;
+import org.apache.lucene.search.TermQuery;
 import org.elasticsearch.common.bytes.BytesReference;
 import org.elasticsearch.common.xcontent.XContentFactory;
 import org.elasticsearch.common.xcontent.XContentType;
@@ -50,10 +52,10 @@ public class ParentJoinFieldMapperTests extends MapperServiceTestCase {
             b.endObject();
         }));
         DocumentMapper docMapper = mapperService.documentMapper();
-        assertSame(
-            docMapper.mappers().getMapper("join_field"),
-            ParentJoinFieldMapper.getMapper(mapperService::fieldType, mapperService.documentMapper().mappers()::getMapper)
-        );
+
+        Joiner joiner = Joiner.getJoiner(f -> mapperService.fieldType(f) != null, mapperService::fieldType);
+        assertNotNull(joiner);
+        assertEquals("join_field", joiner.getJoinField());
 
         // Doc without join
         ParsedDocument doc = docMapper.parse(source(b -> {}));
@@ -118,7 +120,7 @@ public class ParentJoinFieldMapperTests extends MapperServiceTestCase {
         assertEquals("child", doc.rootDoc().getBinaryValue("join_field").utf8ToString());
 
         // Doc child missing parent
-        MapperException exc = expectThrows(
+        MapperParsingException exc = expectThrows(
             MapperParsingException.class,
             () -> docMapper.parse(source("2", b -> b.field("join_field", "child"), "1"))
         );
@@ -174,7 +176,7 @@ public class ParentJoinFieldMapperTests extends MapperServiceTestCase {
             }
             b.endObject();
         })));
-        assertThat(exc.getMessage(), containsString("cannot remove parent [parent] in join field [join_field]"));
+        assertThat(exc.getMessage(), containsString("Cannot remove parent [parent]"));
 
         exc = expectThrows(IllegalArgumentException.class, () -> merge(mapperService, mapping(b -> {
             b.startObject("join_field");
@@ -189,7 +191,7 @@ public class ParentJoinFieldMapperTests extends MapperServiceTestCase {
             }
             b.endObject();
         })));
-        assertThat(exc.getMessage(), containsString("cannot remove child [grand_child2] in join field [join_field]"));
+        assertThat(exc.getMessage(), containsString("Cannot remove child [grand_child2]"));
 
         exc = expectThrows(IllegalArgumentException.class, () -> merge(mapperService, mapping(b -> {
             b.startObject("join_field");
@@ -205,7 +207,7 @@ public class ParentJoinFieldMapperTests extends MapperServiceTestCase {
             }
             b.endObject();
         })));
-        assertThat(exc.getMessage(), containsString("cannot create child [parent] from an existing parent"));
+        assertThat(exc.getMessage(), containsString("Cannot create child [parent] from an existing root"));
 
         exc = expectThrows(IllegalArgumentException.class, () -> merge(mapperService, mapping(b -> {
             b.startObject("join_field");
@@ -221,7 +223,7 @@ public class ParentJoinFieldMapperTests extends MapperServiceTestCase {
             }
             b.endObject();
         })));
-        assertThat(exc.getMessage(), containsString("cannot create parent [grand_child2] from an existing child]"));
+        assertThat(exc.getMessage(), containsString("Cannot create parent [grand_child2] from an existing child"));
 
         merge(mapperService, mapping(b -> {
             b.startObject("join_field");
@@ -236,16 +238,18 @@ public class ParentJoinFieldMapperTests extends MapperServiceTestCase {
             }
             b.endObject();
         }));
-        ParentJoinFieldMapper mapper = ParentJoinFieldMapper.getMapper(
-            mapperService::fieldType,
-            mapperService.documentMapper().mappers()::getMapper
-        );
-        assertNotNull(mapper);
-        assertEquals("join_field", mapper.name());
-        assertTrue(mapper.hasChild("child2"));
-        assertFalse(mapper.hasParent("child2"));
-        assertTrue(mapper.hasChild("grand_child2"));
-        assertFalse(mapper.hasParent("grand_child2"));
+
+        Joiner joiner = Joiner.getJoiner(f -> mapperService.fieldType(f) != null, mapperService::fieldType);
+        assertNotNull(joiner);
+        assertEquals("join_field", joiner.getJoinField());
+        assertTrue(joiner.childTypeExists("child2"));
+        assertFalse(joiner.parentTypeExists("child2"));
+        assertEquals(new TermQuery(new Term("join_field", "parent")), joiner.parentFilter("child"));
+        assertEquals(new TermQuery(new Term("join_field", "parent")), joiner.parentFilter("child2"));
+        assertTrue(joiner.childTypeExists("grand_child2"));
+        assertFalse(joiner.parentTypeExists("grand_child2"));
+        assertEquals(new TermQuery(new Term("join_field", "child")), joiner.parentFilter("grand_child1"));
+        assertEquals(new TermQuery(new Term("join_field", "child")), joiner.parentFilter("grand_child2"));
 
         merge(mapperService, mapping(b -> {
             b.startObject("join_field");
@@ -261,18 +265,24 @@ public class ParentJoinFieldMapperTests extends MapperServiceTestCase {
             }
             b.endObject();
         }));
-        mapper = ParentJoinFieldMapper.getMapper(
-            mapperService::fieldType,
-            mapperService.documentMapper().mappers()::getMapper
-        );
-        assertNotNull(mapper);
-        assertEquals("join_field", mapper.name());
-        assertTrue(mapper.hasParent("other"));
-        assertFalse(mapper.hasChild("other"));
-        assertTrue(mapper.hasChild("child_other1"));
-        assertFalse(mapper.hasParent("child_other1"));
-        assertTrue(mapper.hasChild("child_other2"));
-        assertFalse(mapper.hasParent("child_other2"));
+        joiner = Joiner.getJoiner(f -> mapperService.fieldType(f) != null, mapperService::fieldType);
+        assertNotNull(joiner);
+        assertEquals("join_field", joiner.getJoinField());
+        assertTrue(joiner.childTypeExists("child2"));
+        assertFalse(joiner.parentTypeExists("child2"));
+        assertEquals(new TermQuery(new Term("join_field", "parent")), joiner.parentFilter("child"));
+        assertEquals(new TermQuery(new Term("join_field", "parent")), joiner.parentFilter("child2"));
+        assertTrue(joiner.childTypeExists("grand_child2"));
+        assertFalse(joiner.parentTypeExists("grand_child2"));
+        assertEquals(new TermQuery(new Term("join_field", "child")), joiner.parentFilter("grand_child1"));
+        assertEquals(new TermQuery(new Term("join_field", "child")), joiner.parentFilter("grand_child2"));
+        assertTrue(joiner.parentTypeExists("other"));
+        assertFalse(joiner.childTypeExists("other"));
+        assertTrue(joiner.childTypeExists("child_other1"));
+        assertFalse(joiner.parentTypeExists("child_other1"));
+        assertTrue(joiner.childTypeExists("child_other2"));
+        assertFalse(joiner.parentTypeExists("child_other2"));
+        assertEquals(new TermQuery(new Term("join_field", "other")), joiner.parentFilter("child_other2"));
     }
 
     public void testInvalidJoinFieldInsideObject() throws Exception {

+ 6 - 5
modules/parent-join/src/test/java/org/elasticsearch/join/query/HasChildQueryBuilderTests.java

@@ -67,6 +67,7 @@ import java.util.HashMap;
 import java.util.Map;
 
 import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
+import static org.elasticsearch.join.query.HasChildQueryBuilder.LateParsingQuery;
 import static org.elasticsearch.join.query.JoinQueryBuilders.hasChildQuery;
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.CoreMatchers.equalTo;
@@ -168,8 +169,8 @@ public class HasChildQueryBuilderTests extends AbstractQueryTestCase<HasChildQue
 
     @Override
     protected void doAssertLuceneQuery(HasChildQueryBuilder queryBuilder, Query query, QueryShardContext context) throws IOException {
-        assertThat(query, instanceOf(HasChildQueryBuilder.LateParsingQuery.class));
-        HasChildQueryBuilder.LateParsingQuery lpq = (HasChildQueryBuilder.LateParsingQuery) query;
+        assertThat(query, instanceOf(LateParsingQuery.class));
+        LateParsingQuery lpq = (LateParsingQuery) query;
         assertEquals(queryBuilder.minChildren(), lpq.getMinChildren());
         assertEquals(queryBuilder.maxChildren(), lpq.getMaxChildren());
         assertEquals(queryBuilder.scoreMode(), lpq.getScoreMode()); // WTF is this why do we have two?
@@ -282,8 +283,8 @@ public class HasChildQueryBuilderTests extends AbstractQueryTestCase<HasChildQue
     }
 
     static void assertLateParsingQuery(Query query, String type, String id) throws IOException {
-        assertThat(query, instanceOf(HasChildQueryBuilder.LateParsingQuery.class));
-        HasChildQueryBuilder.LateParsingQuery lateParsingQuery = (HasChildQueryBuilder.LateParsingQuery) query;
+        assertThat(query, instanceOf(LateParsingQuery.class));
+        LateParsingQuery lateParsingQuery = (LateParsingQuery) query;
         assertThat(lateParsingQuery.getInnerQuery(), instanceOf(BooleanQuery.class));
         BooleanQuery booleanQuery = (BooleanQuery) lateParsingQuery.getInnerQuery();
         assertThat(booleanQuery.clauses().size(), equalTo(2));
@@ -322,7 +323,7 @@ public class HasChildQueryBuilderTests extends AbstractQueryTestCase<HasChildQue
         QueryShardContext shardContext = createShardContext();
         HasChildQueryBuilder hasChildQueryBuilder =
             hasChildQuery(CHILD_DOC, new TermQueryBuilder("custom_string", "value"), ScoreMode.None);
-        HasChildQueryBuilder.LateParsingQuery query = (HasChildQueryBuilder.LateParsingQuery) hasChildQueryBuilder.toQuery(shardContext);
+        LateParsingQuery query = (LateParsingQuery) hasChildQueryBuilder.toQuery(shardContext);
         Similarity expected = SimilarityService.BUILT_IN.get(similarity)
             .apply(Settings.EMPTY, Version.CURRENT, null);
         assertThat(((PerFieldSimilarityWrapper) query.getSimilarity()).get("custom_string"), instanceOf(expected.getClass()));

+ 3 - 2
modules/parent-join/src/test/java/org/elasticsearch/join/query/HasParentQueryBuilderTests.java

@@ -40,6 +40,7 @@ import org.elasticsearch.index.query.QueryShardException;
 import org.elasticsearch.index.query.TermQueryBuilder;
 import org.elasticsearch.index.query.WrapperQueryBuilder;
 import org.elasticsearch.join.ParentJoinPlugin;
+import org.elasticsearch.join.query.HasChildQueryBuilder.LateParsingQuery;
 import org.elasticsearch.plugins.Plugin;
 import org.elasticsearch.search.sort.FieldSortBuilder;
 import org.elasticsearch.search.sort.SortOrder;
@@ -142,8 +143,8 @@ public class HasParentQueryBuilderTests extends AbstractQueryTestCase<HasParentQ
 
     @Override
     protected void doAssertLuceneQuery(HasParentQueryBuilder queryBuilder, Query query, QueryShardContext context) throws IOException {
-        assertThat(query, instanceOf(HasChildQueryBuilder.LateParsingQuery.class));
-        HasChildQueryBuilder.LateParsingQuery lpq = (HasChildQueryBuilder.LateParsingQuery) query;
+        assertThat(query, instanceOf(LateParsingQuery.class));
+        LateParsingQuery lpq = (LateParsingQuery) query;
         assertEquals(queryBuilder.score() ? ScoreMode.Max : ScoreMode.None, lpq.getScoreMode());
 
         if (queryBuilder.innerHit() != null) {

+ 4 - 5
server/src/main/java/org/elasticsearch/search/aggregations/support/AggregationContext.java

@@ -25,7 +25,6 @@ import org.elasticsearch.common.util.BigArrays;
 import org.elasticsearch.index.IndexSettings;
 import org.elasticsearch.index.fielddata.IndexFieldData;
 import org.elasticsearch.index.mapper.MappedFieldType;
-import org.elasticsearch.index.mapper.Mapper;
 import org.elasticsearch.index.mapper.ObjectMapper;
 import org.elasticsearch.index.query.QueryBuilder;
 import org.elasticsearch.index.query.QueryShardContext;
@@ -90,9 +89,9 @@ public abstract class AggregationContext {
     public abstract MappedFieldType getFieldType(String path);
 
     /**
-     * Lookup a field {@link Mapper} by path.
+     * Returns true if the field identified by the provided name is mapped, false otherwise
      */
-    public abstract Mapper getMapper(String path);
+    public abstract boolean isFieldMapped(String field);
 
     /**
      * Compile a script.
@@ -188,8 +187,8 @@ public abstract class AggregationContext {
         }
 
         @Override
-        public Mapper getMapper(String path) {
-            return context.getMapperService().documentMapper().mappers().getMapper(path);
+        public boolean isFieldMapped(String field) {
+            return context.isFieldMapped(field);
         }
 
         @Override

+ 1 - 1
server/src/main/java/org/elasticsearch/search/fetch/subphase/highlight/CustomQueryScorer.java

@@ -97,7 +97,7 @@ public final class CustomQueryScorer extends QueryScorer {
         /**
          * Workaround to detect parent/child query
          */
-        private static final String PARENT_CHILD_QUERY_NAME = "HasChildQueryBuilder$LateParsingQuery";
+        private static final String PARENT_CHILD_QUERY_NAME = "LateParsingQuery";
         private static boolean isChildOrParentQuery(Class<?> clazz) {
             return clazz.getName().endsWith(PARENT_CHILD_QUERY_NAME);
         }

+ 3 - 3
test/framework/src/main/java/org/elasticsearch/index/mapper/MapperServiceTestCase.java

@@ -267,9 +267,9 @@ public abstract class MapperServiceTestCase extends ESTestCase {
             public long nowInMillis() {
                 return 0;
             }
-
+            
             @Override
-            public Mapper getMapper(String name) {
+            public boolean isFieldMapped(String field) {
                 throw new UnsupportedOperationException();
             }
 
@@ -337,7 +337,7 @@ public abstract class MapperServiceTestCase extends ESTestCase {
     ) throws IOException {
         withAggregationContext(mapperService, docs, null, test);
     }
-    
+
     protected final void withAggregationContext(
         MapperService mapperService,
         List<SourceToParse> docs,