Browse Source

Adds ignore_unmapped option to nested and P/C queries

The change adds a new option to the `nested`, `has_parent`, `has_children` and `parent_id` queries: `ignore_unmapped`. If this option is set to false, the `toQuery` method on the QueryBuilder will throw an exception if the type/path specified in the query is unmapped. If the option is set to true, the `toQuery` method on the QueryBuilder will return a MatchNoDocsQuery. The default value is `false`so the queries work how they do today (throwing an exception on unmapped paths/types)
Colin Goodheart-Smithe 9 years ago
parent
commit
686aff1545

+ 44 - 5
core/src/main/java/org/elasticsearch/index/query/HasChildQueryBuilder.java

@@ -63,6 +63,11 @@ public class HasChildQueryBuilder extends AbstractQueryBuilder<HasChildQueryBuil
      * The default minimum number of children that are required to match for the parent to be considered a match.
      */
     public static final int DEFAULT_MIN_CHILDREN = 0;
+
+    /**
+     * The default value for ignore_unmapped.
+     */
+    public static final boolean DEFAULT_IGNORE_UNMAPPED = false;
     /*
      * The default score mode that is used to combine score coming from multiple parent documents.
      */
@@ -74,6 +79,7 @@ public class HasChildQueryBuilder extends AbstractQueryBuilder<HasChildQueryBuil
     private static final ParseField MIN_CHILDREN_FIELD = new ParseField("min_children");
     private static final ParseField SCORE_MODE_FIELD = new ParseField("score_mode");
     private static final ParseField INNER_HITS_FIELD = new ParseField("inner_hits");
+    private static final ParseField IGNORE_UNMAPPED_FIELD = new ParseField("ignore_unmapped");
 
     private final QueryBuilder<?> query;
 
@@ -87,6 +93,8 @@ public class HasChildQueryBuilder extends AbstractQueryBuilder<HasChildQueryBuil
 
     private InnerHitBuilder innerHitBuilder;
 
+    private boolean ignoreUnmapped = false;
+
 
     public HasChildQueryBuilder(String type, QueryBuilder<?> query, int maxChildren, int minChildren, ScoreMode scoreMode,
                                 InnerHitBuilder innerHitBuilder) {
@@ -123,6 +131,7 @@ public class HasChildQueryBuilder extends AbstractQueryBuilder<HasChildQueryBuil
         scoreMode = ScoreMode.values()[in.readVInt()];
         query = in.readQuery();
         innerHitBuilder = in.readOptionalWriteable(InnerHitBuilder::new);
+        ignoreUnmapped = in.readBoolean();
     }
 
     @Override
@@ -133,6 +142,7 @@ public class HasChildQueryBuilder extends AbstractQueryBuilder<HasChildQueryBuil
         out.writeVInt(scoreMode.ordinal());
         out.writeQuery(query);
         out.writeOptionalWriteable(innerHitBuilder);
+        out.writeBoolean(ignoreUnmapped);
     }
 
     /**
@@ -220,6 +230,25 @@ public class HasChildQueryBuilder extends AbstractQueryBuilder<HasChildQueryBuil
      */
     public int maxChildren() { return maxChildren; }
 
+    /**
+     * Sets whether the query builder should ignore unmapped types (and run a
+     * {@link MatchNoDocsQuery} in place of this query) or throw an exception if
+     * the type is unmapped.
+     */
+    public HasChildQueryBuilder ignoreUnmapped(boolean ignoreUnmapped) {
+        this.ignoreUnmapped = ignoreUnmapped;
+        return this;
+    }
+
+    /**
+     * Gets whether the query builder will ignore unmapped types (and run a
+     * {@link MatchNoDocsQuery} in place of this query) or throw an exception if
+     * the type is unmapped.
+     */
+    public boolean ignoreUnmapped() {
+        return ignoreUnmapped;
+    }
+
     @Override
     protected void doXContent(XContentBuilder builder, Params params) throws IOException {
         builder.startObject(NAME);
@@ -229,6 +258,7 @@ public class HasChildQueryBuilder extends AbstractQueryBuilder<HasChildQueryBuil
         builder.field(SCORE_MODE_FIELD.getPreferredName(), scoreModeAsString(scoreMode));
         builder.field(MIN_CHILDREN_FIELD.getPreferredName(), minChildren);
         builder.field(MAX_CHILDREN_FIELD.getPreferredName(), maxChildren);
+        builder.field(IGNORE_UNMAPPED_FIELD.getPreferredName(), ignoreUnmapped);
         printBoostAndQueryName(builder);
         if (innerHitBuilder != null) {
             builder.field(INNER_HITS_FIELD.getPreferredName(), innerHitBuilder, params);
@@ -243,6 +273,7 @@ public class HasChildQueryBuilder extends AbstractQueryBuilder<HasChildQueryBuil
         ScoreMode scoreMode = HasChildQueryBuilder.DEFAULT_SCORE_MODE;
         int minChildren = HasChildQueryBuilder.DEFAULT_MIN_CHILDREN;
         int maxChildren = HasChildQueryBuilder.DEFAULT_MAX_CHILDREN;
+        boolean ignoreUnmapped = DEFAULT_IGNORE_UNMAPPED;
         String queryName = null;
         InnerHitBuilder innerHitBuilder = null;
         String currentFieldName = null;
@@ -272,6 +303,8 @@ public class HasChildQueryBuilder extends AbstractQueryBuilder<HasChildQueryBuil
                     minChildren = parser.intValue(true);
                 } else if (parseContext.parseFieldMatcher().match(currentFieldName, MAX_CHILDREN_FIELD)) {
                     maxChildren = parser.intValue(true);
+                } else if (parseContext.parseFieldMatcher().match(currentFieldName, IGNORE_UNMAPPED_FIELD)) {
+                    ignoreUnmapped = parser.booleanValue();
                 } else if (parseContext.parseFieldMatcher().match(currentFieldName, AbstractQueryBuilder.NAME_FIELD)) {
                     queryName = parser.text();
                 } else {
@@ -283,6 +316,7 @@ public class HasChildQueryBuilder extends AbstractQueryBuilder<HasChildQueryBuil
                 scoreMode, innerHitBuilder);
         hasChildQueryBuilder.queryName(queryName);
         hasChildQueryBuilder.boost(boost);
+        hasChildQueryBuilder.ignoreUnmapped(ignoreUnmapped);
         return hasChildQueryBuilder;
     }
 
@@ -331,7 +365,11 @@ public class HasChildQueryBuilder extends AbstractQueryBuilder<HasChildQueryBuil
         }
         DocumentMapper childDocMapper = context.getMapperService().documentMapper(type);
         if (childDocMapper == null) {
-            throw new QueryShardException(context, "[" + NAME + "] no mapping found for type [" + type + "]");
+            if (ignoreUnmapped) {
+                return new MatchNoDocsQuery();
+            } else {
+                throw new QueryShardException(context, "[" + NAME + "] no mapping found for type [" + type + "]");
+            }
         }
         ParentFieldMapper parentFieldMapper = childDocMapper.parentFieldMapper();
         if (parentFieldMapper.active() == false) {
@@ -344,8 +382,8 @@ public class HasChildQueryBuilder extends AbstractQueryBuilder<HasChildQueryBuil
         String parentType = parentFieldMapper.type();
         DocumentMapper parentDocMapper = context.getMapperService().documentMapper(parentType);
         if (parentDocMapper == null) {
-            throw new QueryShardException(context, "[" + NAME + "] Type [" + type + "] points to a non existent parent type ["
-                    + parentType + "]");
+            throw new QueryShardException(context,
+                    "[" + NAME + "] Type [" + type + "] points to a non existent parent type [" + parentType + "]");
         }
 
         if (maxChildren > 0 && maxChildren < minChildren) {
@@ -464,12 +502,13 @@ public class HasChildQueryBuilder extends AbstractQueryBuilder<HasChildQueryBuil
                 && Objects.equals(scoreMode, that.scoreMode)
                 && Objects.equals(minChildren, that.minChildren)
                 && Objects.equals(maxChildren, that.maxChildren)
-                && Objects.equals(innerHitBuilder, that.innerHitBuilder);
+                && Objects.equals(innerHitBuilder, that.innerHitBuilder) 
+                && Objects.equals(ignoreUnmapped, that.ignoreUnmapped);
     }
 
     @Override
     protected int doHashCode() {
-        return Objects.hash(query, type, scoreMode, minChildren, maxChildren, innerHitBuilder);
+        return Objects.hash(query, type, scoreMode, minChildren, maxChildren, innerHitBuilder, ignoreUnmapped);
     }
 
     @Override

+ 43 - 5
core/src/main/java/org/elasticsearch/index/query/HasParentQueryBuilder.java

@@ -20,6 +20,7 @@ package org.elasticsearch.index.query;
 
 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.join.ScoreMode;
 import org.elasticsearch.common.ParseField;
@@ -49,16 +50,23 @@ public class HasParentQueryBuilder extends AbstractQueryBuilder<HasParentQueryBu
 
     public static final boolean DEFAULT_SCORE = false;
 
+    /**
+     * The default value for ignore_unmapped.
+     */
+    public static final boolean DEFAULT_IGNORE_UNMAPPED = false;
+
     private static final ParseField QUERY_FIELD = new ParseField("query", "filter");
     private static final ParseField SCORE_MODE_FIELD = new ParseField("score_mode").withAllDeprecated("score");
     private static final ParseField TYPE_FIELD = new ParseField("parent_type", "type");
     private static final ParseField SCORE_FIELD = new ParseField("score");
     private static final ParseField INNER_HITS_FIELD = new ParseField("inner_hits");
+    private static final ParseField IGNORE_UNMAPPED_FIELD = new ParseField("ignore_unmapped");
 
     private final QueryBuilder<?> query;
     private final String type;
     private boolean score = DEFAULT_SCORE;
     private InnerHitBuilder innerHit;
+    private boolean ignoreUnmapped = false;
 
     /**
      * @param type  The parent type
@@ -94,6 +102,7 @@ public class HasParentQueryBuilder extends AbstractQueryBuilder<HasParentQueryBu
         score = in.readBoolean();
         query = in.readQuery();
         innerHit = in.readOptionalWriteable(InnerHitBuilder::new);
+        ignoreUnmapped = in.readBoolean();
     }
 
     @Override
@@ -102,6 +111,7 @@ public class HasParentQueryBuilder extends AbstractQueryBuilder<HasParentQueryBu
         out.writeBoolean(score);
         out.writeQuery(query);
         out.writeOptionalWriteable(innerHit);
+        out.writeBoolean(ignoreUnmapped);
     }
 
     /**
@@ -150,6 +160,25 @@ public class HasParentQueryBuilder extends AbstractQueryBuilder<HasParentQueryBu
         return innerHit;
     }
 
+    /**
+     * Sets whether the query builder should ignore unmapped types (and run a
+     * {@link MatchNoDocsQuery} in place of this query) or throw an exception if
+     * the type is unmapped.
+     */
+    public HasParentQueryBuilder ignoreUnmapped(boolean ignoreUnmapped) {
+        this.ignoreUnmapped = ignoreUnmapped;
+        return this;
+    }
+
+    /**
+     * Gets whether the query builder will ignore unmapped types (and run a
+     * {@link MatchNoDocsQuery} in place of this query) or throw an exception if
+     * the type is unmapped.
+     */
+    public boolean ignoreUnmapped() {
+        return ignoreUnmapped;
+    }
+
     @Override
     protected Query doToQuery(QueryShardContext context) throws IOException {
         Query innerQuery;
@@ -166,8 +195,11 @@ public class HasParentQueryBuilder extends AbstractQueryBuilder<HasParentQueryBu
         }
         DocumentMapper parentDocMapper = context.getMapperService().documentMapper(type);
         if (parentDocMapper == null) {
-            throw new QueryShardException(context, "[" + NAME + "] query configured 'parent_type' [" + type
-                    + "] is not a valid type");
+            if (ignoreUnmapped) {
+                return new MatchNoDocsQuery();
+            } else {
+                throw new QueryShardException(context, "[" + NAME + "] query configured 'parent_type' [" + type + "] is not a valid type");
+            }
         }
 
         if (innerHit != null) {
@@ -220,6 +252,7 @@ public class HasParentQueryBuilder extends AbstractQueryBuilder<HasParentQueryBu
         query.toXContent(builder, params);
         builder.field(TYPE_FIELD.getPreferredName(), type);
         builder.field(SCORE_FIELD.getPreferredName(), score);
+        builder.field(IGNORE_UNMAPPED_FIELD.getPreferredName(), ignoreUnmapped);
         printBoostAndQueryName(builder);
         if (innerHit != null) {
             builder.field(INNER_HITS_FIELD.getPreferredName(), innerHit, params);
@@ -234,6 +267,7 @@ public class HasParentQueryBuilder extends AbstractQueryBuilder<HasParentQueryBu
         boolean score = HasParentQueryBuilder.DEFAULT_SCORE;
         String queryName = null;
         InnerHitBuilder innerHits = null;
+        boolean ignoreUnmapped = DEFAULT_IGNORE_UNMAPPED;
 
         String currentFieldName = null;
         XContentParser.Token token;
@@ -264,6 +298,8 @@ public class HasParentQueryBuilder extends AbstractQueryBuilder<HasParentQueryBu
                     }
                 } else if (parseContext.parseFieldMatcher().match(currentFieldName, SCORE_FIELD)) {
                     score = parser.booleanValue();
+                } else if (parseContext.parseFieldMatcher().match(currentFieldName, IGNORE_UNMAPPED_FIELD)) {
+                    ignoreUnmapped = parser.booleanValue();
                 } else if (parseContext.parseFieldMatcher().match(currentFieldName, AbstractQueryBuilder.BOOST_FIELD)) {
                     boost = parser.floatValue();
                 } else if (parseContext.parseFieldMatcher().match(currentFieldName, AbstractQueryBuilder.NAME_FIELD)) {
@@ -273,7 +309,8 @@ public class HasParentQueryBuilder extends AbstractQueryBuilder<HasParentQueryBu
                 }
             }
         }
-        return new HasParentQueryBuilder(parentType, iqb, score, innerHits).queryName(queryName).boost(boost);
+        return new HasParentQueryBuilder(parentType, iqb, score, innerHits).ignoreUnmapped(ignoreUnmapped).queryName(queryName)
+                .boost(boost);
     }
 
     @Override
@@ -286,12 +323,13 @@ public class HasParentQueryBuilder extends AbstractQueryBuilder<HasParentQueryBu
         return Objects.equals(query, that.query)
                 && Objects.equals(type, that.type)
                 && Objects.equals(score, that.score)
-                && Objects.equals(innerHit, that.innerHit);
+                && Objects.equals(innerHit, that.innerHit) 
+                && Objects.equals(ignoreUnmapped, that.ignoreUnmapped);
     }
 
     @Override
     protected int doHashCode() {
-        return Objects.hash(query, type, score, innerHit);
+        return Objects.hash(query, type, score, innerHit, ignoreUnmapped);
     }
 
     @Override

+ 45 - 5
core/src/main/java/org/elasticsearch/index/query/NestedQueryBuilder.java

@@ -19,6 +19,7 @@
 
 package org.elasticsearch.index.query;
 
+import org.apache.lucene.search.MatchNoDocsQuery;
 import org.apache.lucene.search.Query;
 import org.apache.lucene.search.join.BitSetProducer;
 import org.apache.lucene.search.join.ScoreMode;
@@ -45,14 +46,20 @@ public class NestedQueryBuilder extends AbstractQueryBuilder<NestedQueryBuilder>
     public static final ParseField QUERY_NAME_FIELD = new ParseField(NAME);
 
     /**
-     * The default score move for nested queries.
+     * The default score mode for nested queries.
      */
     public static final ScoreMode DEFAULT_SCORE_MODE = ScoreMode.Avg;
 
+    /**
+     * The default value for ignore_unmapped.
+     */
+    public static final boolean DEFAULT_IGNORE_UNMAPPED = false;
+
     private static final ParseField SCORE_MODE_FIELD = new ParseField("score_mode");
     private static final ParseField PATH_FIELD = new ParseField("path");
     private static final ParseField QUERY_FIELD = new ParseField("query");
     private static final ParseField INNER_HITS_FIELD = new ParseField("inner_hits");
+    private static final ParseField IGNORE_UNMAPPED_FIELD = new ParseField("ignore_unmapped");
 
     private final QueryBuilder<?> query;
 
@@ -62,6 +69,8 @@ public class NestedQueryBuilder extends AbstractQueryBuilder<NestedQueryBuilder>
 
     private InnerHitBuilder innerHitBuilder;
 
+    private boolean ignoreUnmapped = DEFAULT_IGNORE_UNMAPPED;
+
     public NestedQueryBuilder(String path, QueryBuilder<?> query) {
         if (path == null) {
             throw new IllegalArgumentException("[" + NAME + "] requires 'path' field");
@@ -92,6 +101,7 @@ public class NestedQueryBuilder extends AbstractQueryBuilder<NestedQueryBuilder>
         scoreMode = ScoreMode.values()[in.readVInt()];
         query = in.readQuery();
         innerHitBuilder = in.readOptionalWriteable(InnerHitBuilder::new);
+        ignoreUnmapped = in.readBoolean();
     }
 
     @Override
@@ -100,6 +110,7 @@ public class NestedQueryBuilder extends AbstractQueryBuilder<NestedQueryBuilder>
         out.writeVInt(scoreMode.ordinal());
         out.writeQuery(query);
         out.writeOptionalWriteable(innerHitBuilder);
+        out.writeBoolean(ignoreUnmapped);
     }
 
     /**
@@ -123,6 +134,25 @@ public class NestedQueryBuilder extends AbstractQueryBuilder<NestedQueryBuilder>
         return this;
     }
 
+    /**
+     * Sets whether the query builder should ignore unmapped paths (and run a
+     * {@link MatchNoDocsQuery} in place of this query) or throw an exception if
+     * the path is unmapped.
+     */
+    public NestedQueryBuilder ignoreUnmapped(boolean ignoreUnmapped) {
+        this.ignoreUnmapped = ignoreUnmapped;
+        return this;
+    }
+
+    /**
+     * Gets whether the query builder will ignore unmapped fields (and run a
+     * {@link MatchNoDocsQuery} in place of this query) or throw an exception if
+     * the path is unmapped.
+     */
+    public boolean ignoreUnmapped() {
+        return ignoreUnmapped;
+    }
+
     /**
      * Returns the nested query to execute.
      */
@@ -150,6 +180,7 @@ public class NestedQueryBuilder extends AbstractQueryBuilder<NestedQueryBuilder>
         builder.field(QUERY_FIELD.getPreferredName());
         query.toXContent(builder, params);
         builder.field(PATH_FIELD.getPreferredName(), path);
+        builder.field(IGNORE_UNMAPPED_FIELD.getPreferredName(), ignoreUnmapped);
         if (scoreMode != null) {
             builder.field(SCORE_MODE_FIELD.getPreferredName(), HasChildQueryBuilder.scoreModeAsString(scoreMode));
         }
@@ -169,6 +200,7 @@ public class NestedQueryBuilder extends AbstractQueryBuilder<NestedQueryBuilder>
         String path = null;
         String currentFieldName = null;
         InnerHitBuilder innerHitBuilder = null;
+        boolean ignoreUnmapped = DEFAULT_IGNORE_UNMAPPED;
         XContentParser.Token token;
         while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
             if (token == XContentParser.Token.FIELD_NAME) {
@@ -186,6 +218,8 @@ public class NestedQueryBuilder extends AbstractQueryBuilder<NestedQueryBuilder>
                     path = parser.text();
                 } else if (parseContext.parseFieldMatcher().match(currentFieldName, AbstractQueryBuilder.BOOST_FIELD)) {
                     boost = parser.floatValue();
+                } else if (parseContext.parseFieldMatcher().match(currentFieldName, IGNORE_UNMAPPED_FIELD)) {
+                    ignoreUnmapped = parser.booleanValue();
                 } else if (parseContext.parseFieldMatcher().match(currentFieldName, SCORE_MODE_FIELD)) {
                     scoreMode = HasChildQueryBuilder.parseScoreMode(parser.text());
                 } else if (parseContext.parseFieldMatcher().match(currentFieldName, AbstractQueryBuilder.NAME_FIELD)) {
@@ -195,7 +229,8 @@ public class NestedQueryBuilder extends AbstractQueryBuilder<NestedQueryBuilder>
                 }
             }
         }
-        return new NestedQueryBuilder(path, query, scoreMode, innerHitBuilder).queryName(queryName).boost(boost);
+        return new NestedQueryBuilder(path, query, scoreMode, innerHitBuilder).ignoreUnmapped(ignoreUnmapped).queryName(queryName)
+                .boost(boost);
     }
 
     @Override
@@ -208,19 +243,24 @@ public class NestedQueryBuilder extends AbstractQueryBuilder<NestedQueryBuilder>
         return Objects.equals(query, that.query)
                 && Objects.equals(path, that.path)
                 && Objects.equals(scoreMode, that.scoreMode)
-                && Objects.equals(innerHitBuilder, that.innerHitBuilder);
+                && Objects.equals(innerHitBuilder, that.innerHitBuilder) 
+                && Objects.equals(ignoreUnmapped, that.ignoreUnmapped);
     }
 
     @Override
     protected int doHashCode() {
-        return Objects.hash(query, path, scoreMode, innerHitBuilder);
+        return Objects.hash(query, path, scoreMode, innerHitBuilder, ignoreUnmapped);
     }
 
     @Override
     protected Query doToQuery(QueryShardContext context) throws IOException {
         ObjectMapper nestedObjectMapper = context.getObjectMapper(path);
         if (nestedObjectMapper == null) {
-            throw new IllegalStateException("[" + NAME + "] failed to find nested object under path [" + path + "]");
+            if (ignoreUnmapped) {
+                return new MatchNoDocsQuery();
+            } else {
+                throw new IllegalStateException("[" + NAME + "] failed to find nested object under path [" + path + "]");
+            }
         }
         if (!nestedObjectMapper.nested().isNested()) {
             throw new IllegalStateException("[" + NAME + "] nested object under path [" + path + "] is not of nested type");

+ 44 - 3
core/src/main/java/org/elasticsearch/index/query/ParentIdQueryBuilder.java

@@ -23,6 +23,7 @@ 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.common.ParseField;
@@ -43,12 +44,20 @@ public final class ParentIdQueryBuilder extends AbstractQueryBuilder<ParentIdQue
     public static final String NAME = "parent_id";
     public static final ParseField QUERY_NAME_FIELD = new ParseField(NAME);
 
+    /**
+     * The default value for ignore_unmapped.
+     */
+    public static final boolean DEFAULT_IGNORE_UNMAPPED = false;
+
     private static final ParseField ID_FIELD = new ParseField("id");
     private static final ParseField TYPE_FIELD = new ParseField("type", "child_type");
+    private static final ParseField IGNORE_UNMAPPED_FIELD = new ParseField("ignore_unmapped");
 
     private final String type;
     private final String id;
 
+    private boolean ignoreUnmapped = false;
+
     public ParentIdQueryBuilder(String type, String id) {
         this.type = type;
         this.id = id;
@@ -61,12 +70,14 @@ public final class ParentIdQueryBuilder extends AbstractQueryBuilder<ParentIdQue
         super(in);
         type = in.readString();
         id = in.readString();
+        ignoreUnmapped = in.readBoolean();
     }
 
     @Override
     protected void doWriteTo(StreamOutput out) throws IOException {
         out.writeString(type);
         out.writeString(id);
+        out.writeBoolean(ignoreUnmapped);
     }
 
     public String getType() {
@@ -77,11 +88,31 @@ public final class ParentIdQueryBuilder extends AbstractQueryBuilder<ParentIdQue
         return id;
     }
 
+    /**
+     * Sets whether the query builder should ignore unmapped types (and run a
+     * {@link MatchNoDocsQuery} in place of this query) or throw an exception if
+     * the type is unmapped.
+     */
+    public ParentIdQueryBuilder ignoreUnmapped(boolean ignoreUnmapped) {
+        this.ignoreUnmapped = ignoreUnmapped;
+        return this;
+    }
+
+    /**
+     * Gets whether the query builder will ignore unmapped types (and run a
+     * {@link MatchNoDocsQuery} in place of this query) or throw an exception if
+     * the type is unmapped.
+     */
+    public boolean ignoreUnmapped() {
+        return ignoreUnmapped;
+    }
+
     @Override
     protected void doXContent(XContentBuilder builder, Params params) throws IOException {
         builder.startObject(NAME);
         builder.field(TYPE_FIELD.getPreferredName(), type);
         builder.field(ID_FIELD.getPreferredName(), id);
+        builder.field(IGNORE_UNMAPPED_FIELD.getPreferredName(), ignoreUnmapped);
         printBoostAndQueryName(builder);
         builder.endObject();
     }
@@ -93,6 +124,7 @@ public final class ParentIdQueryBuilder extends AbstractQueryBuilder<ParentIdQue
         String id = null;
         String queryName = null;
         String currentFieldName = null;
+        boolean ignoreUnmapped = DEFAULT_IGNORE_UNMAPPED;
         XContentParser.Token token;
         while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
             if (token == XContentParser.Token.FIELD_NAME) {
@@ -102,6 +134,8 @@ public final class ParentIdQueryBuilder extends AbstractQueryBuilder<ParentIdQue
                     type = parser.text();
                 } else if (parseContext.parseFieldMatcher().match(currentFieldName, ID_FIELD)) {
                     id = parser.text();
+                } else if (parseContext.parseFieldMatcher().match(currentFieldName, IGNORE_UNMAPPED_FIELD)) {
+                    ignoreUnmapped = parser.booleanValue();
                 } else if (parseContext.parseFieldMatcher().match(currentFieldName, AbstractQueryBuilder.BOOST_FIELD)) {
                     boost = parser.floatValue();
                 } else if (parseContext.parseFieldMatcher().match(currentFieldName, AbstractQueryBuilder.NAME_FIELD)) {
@@ -116,6 +150,7 @@ public final class ParentIdQueryBuilder extends AbstractQueryBuilder<ParentIdQue
         ParentIdQueryBuilder queryBuilder = new ParentIdQueryBuilder(type, id);
         queryBuilder.queryName(queryName);
         queryBuilder.boost(boost);
+        queryBuilder.ignoreUnmapped(ignoreUnmapped);
         return queryBuilder;
     }
 
@@ -124,7 +159,11 @@ public final class ParentIdQueryBuilder extends AbstractQueryBuilder<ParentIdQue
     protected Query doToQuery(QueryShardContext context) throws IOException {
         DocumentMapper childDocMapper = context.getMapperService().documentMapper(type);
         if (childDocMapper == null) {
-            throw new QueryShardException(context, "[" + NAME + "] no mapping found for type [" + type + "]");
+            if (ignoreUnmapped) {
+                return new MatchNoDocsQuery();
+            } else {
+                throw new QueryShardException(context, "[" + NAME + "] no mapping found for type [" + type + "]");
+            }
         }
         ParentFieldMapper parentFieldMapper = childDocMapper.parentFieldMapper();
         if (parentFieldMapper.active() == false) {
@@ -141,12 +180,14 @@ public final class ParentIdQueryBuilder extends AbstractQueryBuilder<ParentIdQue
 
     @Override
     protected boolean doEquals(ParentIdQueryBuilder that) {
-        return Objects.equals(type, that.type) && Objects.equals(id, that.id);
+        return Objects.equals(type, that.type)
+                && Objects.equals(id, that.id)
+                && Objects.equals(ignoreUnmapped, that.ignoreUnmapped);
     }
 
     @Override
     protected int doHashCode() {
-        return Objects.hash(type, id);
+        return Objects.hash(type, id, ignoreUnmapped);
     }
 
     @Override

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

@@ -26,6 +26,7 @@ import org.apache.lucene.queries.TermsQuery;
 import org.apache.lucene.search.BooleanClause;
 import org.apache.lucene.search.BooleanQuery;
 import org.apache.lucene.search.ConstantScoreQuery;
+import org.apache.lucene.search.MatchNoDocsQuery;
 import org.apache.lucene.search.Query;
 import org.apache.lucene.search.TermQuery;
 import org.apache.lucene.search.join.ScoreMode;
@@ -55,9 +56,10 @@ import org.junit.BeforeClass;
 import java.io.IOException;
 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.Matchers.containsString;
+import static org.hamcrest.CoreMatchers.notNullValue;
 import static org.hamcrest.Matchers.is;
 
 public class HasChildQueryBuilderTests extends AbstractQueryTestCase<HasChildQueryBuilder> {
@@ -124,7 +126,7 @@ public class HasChildQueryBuilderTests extends AbstractQueryTestCase<HasChildQue
                 randomBoolean()  ? null : new InnerHitBuilder()
                         .setName(randomAsciiOfLengthBetween(1, 10))
                         .setSize(randomIntBetween(0, 100))
-                        .addSort(new FieldSortBuilder(STRING_FIELD_NAME_2).order(SortOrder.ASC)));
+                        .addSort(new FieldSortBuilder(STRING_FIELD_NAME_2).order(SortOrder.ASC))).ignoreUnmapped(randomBoolean());
     }
 
     @Override
@@ -217,6 +219,7 @@ public class HasChildQueryBuilderTests extends AbstractQueryTestCase<HasChildQue
                 "    \"score_mode\" : \"avg\",\n" +
                 "    \"min_children\" : 883170873,\n" +
                 "    \"max_children\" : 1217235442,\n" +
+                "    \"ignore_unmapped\" : false,\n" +
                 "    \"boost\" : 2.0,\n" +
                 "    \"_name\" : \"WNzYMJKRwePuRBh\",\n" +
                 "    \"inner_hits\" : {\n" +
@@ -386,4 +389,17 @@ public class HasChildQueryBuilderTests extends AbstractQueryTestCase<HasChildQue
             assertThat(e.getMessage(), is("No score mode for child query [unrecognized value] found"));
         }
     }
+
+    public void testIgnoreUnmapped() throws IOException {
+        final HasChildQueryBuilder queryBuilder = new HasChildQueryBuilder("unmapped", new MatchAllQueryBuilder());
+        queryBuilder.ignoreUnmapped(true);
+        Query query = queryBuilder.toQuery(queryShardContext());
+        assertThat(query, notNullValue());
+        assertThat(query, instanceOf(MatchNoDocsQuery.class));
+
+        final HasChildQueryBuilder failingQueryBuilder = new HasChildQueryBuilder("unmapped", new MatchAllQueryBuilder());
+        failingQueryBuilder.ignoreUnmapped(false);
+        QueryShardException e = expectThrows(QueryShardException.class, () -> failingQueryBuilder.toQuery(queryShardContext()));
+        assertThat(e.getMessage(), containsString("[" + HasChildQueryBuilder.NAME + "] no mapping found for type [unmapped]"));
+    }
 }

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

@@ -21,6 +21,8 @@ package org.elasticsearch.index.query;
 
 import com.carrotsearch.randomizedtesting.generators.RandomPicks;
 import com.fasterxml.jackson.core.JsonParseException;
+
+import org.apache.lucene.search.MatchNoDocsQuery;
 import org.apache.lucene.search.Query;
 import org.apache.lucene.search.join.ScoreMode;
 import org.elasticsearch.ElasticsearchParseException;
@@ -45,9 +47,10 @@ import org.junit.BeforeClass;
 import java.io.IOException;
 import java.util.Arrays;
 
+import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.CoreMatchers.equalTo;
 import static org.hamcrest.CoreMatchers.instanceOf;
-import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.CoreMatchers.notNullValue;
 
 public class HasParentQueryBuilderTests extends AbstractQueryTestCase<HasParentQueryBuilder> {
     protected static final String PARENT_TYPE = "parent";
@@ -109,7 +112,7 @@ public class HasParentQueryBuilderTests extends AbstractQueryTestCase<HasParentQ
                 randomBoolean() ? null : new InnerHitBuilder()
                         .setName(randomAsciiOfLengthBetween(1, 10))
                         .setSize(randomIntBetween(0, 100))
-                        .addSort(new FieldSortBuilder(STRING_FIELD_NAME_2).order(SortOrder.ASC)));
+                        .addSort(new FieldSortBuilder(STRING_FIELD_NAME_2).order(SortOrder.ASC))).ignoreUnmapped(randomBoolean());
     }
 
     @Override
@@ -257,6 +260,7 @@ public class HasParentQueryBuilderTests extends AbstractQueryTestCase<HasParentQ
                 "    },\n" +
                 "    \"parent_type\" : \"blog\",\n" +
                 "    \"score\" : true,\n" +
+                "    \"ignore_unmapped\" : false,\n" +
                 "    \"boost\" : 1.0\n" +
                 "  }\n" +
                 "}";
@@ -265,4 +269,18 @@ public class HasParentQueryBuilderTests extends AbstractQueryTestCase<HasParentQ
         assertEquals(json, "blog", parsed.type());
         assertEquals(json, "something", ((TermQueryBuilder) parsed.query()).value());
     }
+
+    public void testIgnoreUnmapped() throws IOException {
+        final HasParentQueryBuilder queryBuilder = new HasParentQueryBuilder("unmapped", new MatchAllQueryBuilder());
+        queryBuilder.ignoreUnmapped(true);
+        Query query = queryBuilder.toQuery(queryShardContext());
+        assertThat(query, notNullValue());
+        assertThat(query, instanceOf(MatchNoDocsQuery.class));
+
+        final HasParentQueryBuilder failingQueryBuilder = new HasParentQueryBuilder("unmapped", new MatchAllQueryBuilder());
+        failingQueryBuilder.ignoreUnmapped(false);
+        QueryShardException e = expectThrows(QueryShardException.class, () -> failingQueryBuilder.toQuery(queryShardContext()));
+        assertThat(e.getMessage(),
+                    containsString("[" + HasParentQueryBuilder.NAME + "] query configured 'parent_type' [unmapped] is not a valid type"));
+    }
 }

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

@@ -20,6 +20,8 @@
 package org.elasticsearch.index.query;
 
 import com.carrotsearch.randomizedtesting.generators.RandomPicks;
+
+import org.apache.lucene.search.MatchNoDocsQuery;
 import org.apache.lucene.search.Query;
 import org.apache.lucene.search.join.ScoreMode;
 import org.apache.lucene.search.join.ToParentBlockJoinQuery;
@@ -38,6 +40,8 @@ import java.io.IOException;
 
 import static org.hamcrest.CoreMatchers.instanceOf;
 import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.CoreMatchers.containsString;
 
 public class NestedQueryBuilderTests extends AbstractQueryTestCase<NestedQueryBuilder> {
 
@@ -87,7 +91,7 @@ public class NestedQueryBuilderTests extends AbstractQueryTestCase<NestedQueryBu
                 SearchContext.current() == null ? null : new InnerHitBuilder()
                         .setName(randomAsciiOfLengthBetween(1, 10))
                         .setSize(randomIntBetween(0, 100))
-                        .addSort(new FieldSortBuilder(STRING_FIELD_NAME).order(SortOrder.ASC)));
+                        .addSort(new FieldSortBuilder(STRING_FIELD_NAME).order(SortOrder.ASC))).ignoreUnmapped(randomBoolean());
     }
 
     @Override
@@ -176,6 +180,7 @@ public class NestedQueryBuilderTests extends AbstractQueryTestCase<NestedQueryBu
                 "      }\n" +
                 "    },\n" +
                 "    \"path\" : \"obj1\",\n" +
+                "    \"ignore_unmapped\" : false,\n" +
                 "    \"score_mode\" : \"avg\",\n" +
                 "    \"boost\" : 1.0\n" +
                 "  }\n" +
@@ -186,4 +191,17 @@ public class NestedQueryBuilderTests extends AbstractQueryTestCase<NestedQueryBu
 
         assertEquals(json, ScoreMode.Avg, parsed.scoreMode());
     }
+
+    public void testIgnoreUnmapped() throws IOException {
+        final NestedQueryBuilder queryBuilder = new NestedQueryBuilder("unmapped", new MatchAllQueryBuilder());
+        queryBuilder.ignoreUnmapped(true);
+        Query query = queryBuilder.toQuery(queryShardContext());
+        assertThat(query, notNullValue());
+        assertThat(query, instanceOf(MatchNoDocsQuery.class));
+
+        final NestedQueryBuilder failingQueryBuilder = new NestedQueryBuilder("unmapped", new MatchAllQueryBuilder());
+        failingQueryBuilder.ignoreUnmapped(false);
+        IllegalStateException e = expectThrows(IllegalStateException.class, () -> failingQueryBuilder.toQuery(queryShardContext()));
+        assertThat(e.getMessage(), containsString("[" + NestedQueryBuilder.NAME + "] failed to find nested object under path [unmapped]"));
+    }
 }

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

@@ -21,6 +21,7 @@ package org.elasticsearch.index.query;
 
 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.action.admin.indices.mapping.put.PutMappingRequest;
@@ -34,6 +35,10 @@ import org.hamcrest.Matchers;
 
 import java.io.IOException;
 
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.CoreMatchers.notNullValue;
+
 public class ParentIdQueryBuilderTests extends AbstractQueryTestCase<ParentIdQueryBuilder> {
 
     protected static final String PARENT_TYPE = "parent";
@@ -84,7 +89,7 @@ public class ParentIdQueryBuilderTests extends AbstractQueryTestCase<ParentIdQue
 
     @Override
     protected ParentIdQueryBuilder doCreateTestQueryBuilder() {
-        return new ParentIdQueryBuilder(CHILD_TYPE, randomAsciiOfLength(4));
+        return new ParentIdQueryBuilder(CHILD_TYPE, randomAsciiOfLength(4)).ignoreUnmapped(randomBoolean());
     }
 
     @Override
@@ -108,6 +113,7 @@ public class ParentIdQueryBuilderTests extends AbstractQueryTestCase<ParentIdQue
                 "  \"parent_id\" : {\n" +
                 "    \"type\" : \"child\",\n" +
                 "    \"id\" : \"123\",\n" +
+                "    \"ignore_unmapped\" : false,\n" +
                 "    \"boost\" : 3.0,\n" +
                 "    \"_name\" : \"name\"" +
                 "  }\n" +
@@ -120,4 +126,17 @@ public class ParentIdQueryBuilderTests extends AbstractQueryTestCase<ParentIdQue
         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(queryShardContext());
+        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(queryShardContext()));
+        assertThat(e.getMessage(), containsString("[" + ParentIdQueryBuilder.NAME + "] no mapping found for type [unmapped]"));
+    }
+
 }

+ 9 - 0
docs/reference/query-dsl/has-child-query.asciidoc

@@ -72,3 +72,12 @@ a match:
 
 The  `min_children` and `max_children` parameters can be combined with
 the `score_mode` parameter.
+
+[float]
+==== Ignore Unmapped
+
+When set to `true` the `ignore_unmapped` option will ignore an unmapped `type`
+and will not match any documents for this query. This can be useful when
+querying multiple indexes which might have different mappings. When set to
+`false` (the default value) the query will throw an exception if the `type`
+is not mapped.

+ 9 - 0
docs/reference/query-dsl/has-parent-query.asciidoc

@@ -46,3 +46,12 @@ matching parent document. The score mode can be specified with the
     }
 }
 --------------------------------------------------
+
+[float]
+==== Ignore Unmapped
+
+When set to `true` the `ignore_unmapped` option will ignore an unmapped `type`
+and will not match any documents for this query. This can be useful when
+querying multiple indexes which might have different mappings. When set to
+`false` (the default value) the query will throw an exception if the `type`
+is not mapped.

+ 6 - 0
docs/reference/query-dsl/nested-query.asciidoc

@@ -55,6 +55,12 @@ The `score_mode` allows to set how inner children matching affects
 scoring of parent. It defaults to `avg`, but can be `sum`, `min`,
 `max` and `none`.
 
+There is also an `ignore_unmapped` option which, when set to `true` will
+ignore an unmapped `path` and will not match any documents for this query.
+This can be useful when querying multiple indexes which might have different
+mappings. When set to `false` (the default value) the query will throw an
+exception if the `path` is not mapped.
+
 Multi level nesting is automatically supported, and detected, resulting
 in an inner nested query to automatically match the relevant nesting
 level (and not root) if it exists within another nested query.

+ 10 - 5
docs/reference/query-dsl/parent-id-query.asciidoc

@@ -8,10 +8,10 @@ The `parent_id` query can be used to find child documents which belong to a part
 [source,js]
 --------------------------------------------------
 {
-  "parent_id" : {
-    "type" : "blog_tag",
-    "id" : "1"
-  }
+    "parent_id" : {
+        "type" : "blog_tag",
+        "id" : "1"
+    }
 }
 --------------------------------------------------
 
@@ -40,4 +40,9 @@ This query has two required parameters:
 [horizontal]
 `type`::  The **child** type. This must be a type with `_parent` field.
 
-`id`::    The required parent id select documents must referrer to.
+`id`::    The required parent id select documents must referrer to.
+
+`ignore_unmapped`::  When set to `true` this will ignore an unmapped `type` and will not match any 
+documents for this query. This can be useful when querying multiple indexes
+which might have different mappings. When set to `false` (the default value)
+the query will throw an exception if the `type` is not mapped.